36 Commits

Author SHA1 Message Date
8cf7370cae Allow changing the case of name of an organisation only 2023-10-16 13:30:47 +01:00
bf2a772566 Added user admin permissions 2023-10-06 08:45:12 +01:00
ddb7e645a0 Fixed filtering of the merge dialog 2023-10-04 15:59:49 +01:00
544b4fab13 Uplifted database version to 1.0.0 2023-09-25 14:33:09 +01:00
74dd06a36c Fix syntax error in permission string 2023-09-13 09:08:25 +01:00
b3b4c19e6d Another attempt to fix some permissions 2023-09-13 09:03:59 +01:00
7fab295b6f Fixed some permission issues 2023-09-12 16:02:00 +01:00
dead350676 Tighten organisation admin permissions & password reset workflow 2023-09-07 11:53:39 +01:00
f3af94a285 Fixed a bug in organisation admin permission check 2023-09-07 10:13:51 +01:00
daa4e78a27 Fixed check for has_organisation 2023-08-23 08:39:27 +01:00
1e0f2017f6 Fixed static function trait 2023-08-23 08:39:10 +01:00
cd12ac72c7 Fixed Info column filter 2023-07-25 16:47:42 +01:00
0f5140d01f Simplified the organisation column permissions for now 2023-07-18 13:29:34 +01:00
85b1cec060 Added a has_organisation authorizer check 2023-07-18 13:28:50 +01:00
7f9d329295 Fixed sidebar block 2023-07-18 13:27:42 +01:00
b54945cad6 Moved the organisation actions menu into blocks 2023-06-07 15:02:46 +01:00
522718f7ee Re-built organisation permissions 2023-06-07 13:41:14 +01:00
0ca0f83ac4 Removed dependency on blockier-templates 2023-06-07 13:29:24 +01:00
fe03ad58af Removed namespace that is not used 2023-06-07 13:29:11 +01:00
577ebd0377 Fix findUnique issues 2023-06-07 13:28:58 +01:00
58a1b2d316 No longer need to override authorization manager 2023-06-07 11:12:32 +01:00
de85fd6e47 Replaced "can_admin_via_orgs" auth check & added an explicit flag to the members check 2023-06-06 15:52:54 +01:00
85175f2ead Show/hide columns programatically 2023-06-06 15:51:06 +01:00
3a097fabca Use specific permission for showing deleted organisations button 2023-06-06 15:50:36 +01:00
8ee58f53be Removed blockier-templates dependency 2023-06-06 15:50:18 +01:00
6afe2e0be4 Added organisations to account settings 2023-06-06 15:49:23 +01:00
fc47f23615 Added link to organisations from user menu 2023-06-06 15:49:09 +01:00
b7d15dc25e Removed hasRole twig function 2023-06-06 15:48:53 +01:00
3d21ab4950 Removed token logger service 2023-06-06 15:48:37 +01:00
d9d7bfdd3c Fixed locale typo 2023-06-06 15:48:05 +01:00
5bbceeac9a Change table template hierarchy structure 2023-06-06 15:10:02 +01:00
b8cb04cd14 Abstract the organisation form and sidebar menu 2023-06-06 15:09:14 +01:00
cbf6b916c3 Moved the BasicTokenRepository code to UFTweaks 2023-06-06 15:08:31 +01:00
c99a277e2a Added an administrator fallback banner to organisation join approval emails 2022-11-14 16:54:52 +00:00
5a0906eb43 Organisation admins can view the user pages of their members 2022-05-19 16:45:13 +01:00
a820fb56e0 Moved auditer into a sprinkle of it's own 2022-05-19 16:44:21 +01:00
67 changed files with 1510 additions and 1482 deletions

View File

@@ -137,6 +137,33 @@ function bindMemberButtons(el, options) {
}); });
}); });
}); });
// Reset member password button
el.find('.js-member-password').click(function(e) {
e.preventDefault();
var userName = $(this).data('user_name');
$("body").ufModal({
sourceUrl: site.uri.public + '/modals/organisations/o/' + $(this).data('slug') + '/members/reset-password',
ajaxParams: {
slug: $(this).data('slug'),
user_name: $(this).data('user_name')
},
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
window.location.reload();
});
});
});
} }
// function bindMemberCreationButton(el) { // function bindMemberCreationButton(el) {

View File

@@ -189,6 +189,13 @@ function bindOrganisationButtons(el, options) {
var options = { var options = {
ajax: { ajax: {
url: site.uri.public + '/api/organisations', url: site.uri.public + '/api/organisations',
data: function (params) {
return {
filters: {
info : params.term
}
};
},
processResults: function (data) { processResults: function (data) {
var items = data.rows.filter((i) => i.slug != organisation_slug); var items = data.rows.filter((i) => i.slug != organisation_slug);
return { return {

View File

@@ -15,9 +15,7 @@
* SMTP server password: SMTP_PASSWORD * SMTP server password: SMTP_PASSWORD
*/ */
return [ return [
'debug' => [ 'debug' => [ 'auth' => true ],
'tokens' => true,
],
'organisation' => [ 'organisation' => [
'registration' => [ 'registration' => [
'require_approval' => true, 'require_approval' => true,
@@ -28,5 +26,6 @@ return [
'single_membership' => false, 'single_membership' => false,
'timeout' => -1, 'timeout' => -1,
], ],
'combine_action_buttons' => false,
], ],
]; ];

View File

@@ -28,7 +28,7 @@ return [
'CREATE' => 'Create organisation', 'CREATE' => 'Create organisation',
'CREATION_SUCCESSFUL' => 'Successfully created organisation <strong>{{name}}</strong>', 'CREATION_SUCCESSFUL' => 'Successfully created organisation <strong>{{name}}</strong>',
'EDIT' => 'Edit organistion', 'EDIT' => 'Edit organisation',
'UPDATE' => 'Details updated for organistion <strong>{{name}}</strong>', 'UPDATE' => 'Details updated for organistion <strong>{{name}}</strong>',
@@ -51,7 +51,7 @@ return [
'DELETE_YES' => 'Yes, delete organisation', 'DELETE_YES' => 'Yes, delete organisation',
'DELETION_SUCCESSFUL' => 'Successfully deleted organisation <strong>{{name}}</strong>', 'DELETION_SUCCESSFUL' => 'Successfully deleted organisation <strong>{{name}}</strong>',
'MEMBER_COUNT' => '# Members <sub>(excl admins)</sub>', 'MEMBER_COUNT' => '# Members',
'ADMIN_COUNT' => '# Admins', 'ADMIN_COUNT' => '# Admins',
'SELF' => [ 'SELF' => [
@@ -155,6 +155,10 @@ return [
'DEMOTE_CONFIRM_EXTRA' => 'Once demoted they will no longer be able to manage members and agents on the organisation\'s behalf.', 'DEMOTE_CONFIRM_EXTRA' => 'Once demoted they will no longer be able to manage members and agents on the organisation\'s behalf.',
'DEMOTE_YES' => 'Yes, demote administrator', 'DEMOTE_YES' => 'Yes, demote administrator',
'DEMOTE_SUCCESSFUL' => 'Successfully demoted administrator <strong>{{user_name}}</strong> from being an administrator of organisation <strong>{{name}}</strong>', 'DEMOTE_SUCCESSFUL' => 'Successfully demoted administrator <strong>{{user_name}}</strong> from being an administrator of organisation <strong>{{name}}</strong>',
'RESET_PASSWORD' => 'Send password reset link',
'SEND_PASSWORD_LINK_CONFIRM' => 'Please confirm that you wish to send a password reset link to:<br><br> <strong>{{user_name}}</strong> ({{email}}).',
'PASSWORD_LINK_SENT' => 'A password reset link has successfully been sent to <strong>{{email}}</strong>.'
], ],
], ],
@@ -168,7 +172,7 @@ return [
'DEMOTE' => 'Demote to member', 'DEMOTE' => 'Demote to member',
'EDIT' => 'Edit member', 'EDIT' => 'Edit member',
'CHANGE_PASSWORD' => 'Change member password', 'RESET_PASSWORD' => 'Send password reset link',
], ],
'ADMIN' => [ 'ADMIN' => [
@@ -202,7 +206,10 @@ return [
'DELETED' => 'Deleted', 'DELETED' => 'Deleted',
'RETURN' => 'Return', 'RETURN' => 'Return',
'JOIN' => 'Join', 'JOIN' => [
1 => 'Join',
'CANCEL' => 'Cancel join request',
],
'LEAVE' => 'Leave', 'LEAVE' => 'Leave',
'ACTION_CANNOT_UNDONE' => 'This action cannot be undone!', 'ACTION_CANNOT_UNDONE' => 'This action cannot be undone!',

View File

@@ -33,6 +33,8 @@ $app->group('/api/organisations/o/{slug}/members', function () {
$this->put('/m/{user_name}/promote', 'UserFrosting\Sprinkle\Organisations\Controller\OrganisationMembersController:promote'); $this->put('/m/{user_name}/promote', 'UserFrosting\Sprinkle\Organisations\Controller\OrganisationMembersController:promote');
$this->put('/m/{user_name}/demote', 'UserFrosting\Sprinkle\Organisations\Controller\OrganisationMembersController:demote'); $this->put('/m/{user_name}/demote', 'UserFrosting\Sprinkle\Organisations\Controller\OrganisationMembersController:demote');
$this->post('/m/{user_name}/password-reset', 'UserFrosting\Sprinkle\Organisations\Controller\OrganisationMembersController:createPasswordReset');
})->add('authGuard')->add(new NoCache()); })->add('authGuard')->add(new NoCache());
@@ -45,4 +47,5 @@ $app->group('/modals/organisations/o/{slug}/members', function () {
$this->get('/confirm-reject', 'UserFrosting\Sprinkle\Organisations\Controller\OrganisationMembersController:getModalConfirmReject'); $this->get('/confirm-reject', 'UserFrosting\Sprinkle\Organisations\Controller\OrganisationMembersController:getModalConfirmReject');
$this->get('/confirm-promote', 'UserFrosting\Sprinkle\Organisations\Controller\OrganisationMembersController:getModalConfirmPromote'); $this->get('/confirm-promote', 'UserFrosting\Sprinkle\Organisations\Controller\OrganisationMembersController:getModalConfirmPromote');
$this->get('/confirm-demote', 'UserFrosting\Sprinkle\Organisations\Controller\OrganisationMembersController:getModalConfirmDemote'); $this->get('/confirm-demote', 'UserFrosting\Sprinkle\Organisations\Controller\OrganisationMembersController:getModalConfirmDemote');
$this->get('/reset-password', 'UserFrosting\Sprinkle\Organisations\Controller\OrganisationMembersController:getModalResetPassword');
})->add('authGuard')->add(new NoCache()); })->add('authGuard')->add(new NoCache());

View File

@@ -1,94 +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\Authorize;
use Illuminate\Support\Arr;
use Psr\Container\ContainerInterface;
use UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface;
use UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager as UFAuthorizationManager;
/**
* AuthorizationManager class.
*
* Extends the authorization manager and allows for running an authorization callback without fetching it from the internal list.
*
* @author Craig Williams (https://avsdev.uk)
*/
class AuthorizationManager extends UFAuthorizationManager
{
/**
* Run a registered callback directly.
*
* @param string $name
* @param object $user
* @param ... $args
*/
public function runCallback($user, $name, ...$args)
{
$debug = $this->ci->config['debug.auth'];
$logger = $this->ci->authLogger;
if (is_null($user) || !($user instanceof UserInterface)) {
if ($debug) {
$this->ci->authLogger->debug('No user defined. Access denied.');
}
return false;
}
// The master (root) account has access to everything.
// Need to use loose comparison for now, because some DBs return `id` as a string.
if ($user->id == $this->ci->config['reserved_user_ids.master']) {
if ($debug) {
$this->ci->authLogger->debug('User is the master (root) user. Access granted.');
}
return true;
}
if ($debug) {
$trace = array_slice(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3), 1);
$this->ci->authLogger->debug('Authorization check requested at: ', $trace);
$this->ci->authLogger->debug("Checking authorization for user {$user->id} ('{$user->user_name}') against check '$name'...");
}
if (!array_key_exists($name, $this->callbacks) || !isset($this->callbacks[$name])) {
if ($debug) {
$this->ci->authLogger->debug('No matching callback found. Access denied.');
}
return false;
}
try {
if ($debug) {
$this->ci->authLogger->debug("Calling check '{$name}' with arguments:", $args);
}
$result = call_user_func_array($this->callbacks[$name], $args);
if ($result === true) {
if ($debug) {
$this->ci->authLogger->debug("User passed check '{$name}'. Access granted.");
}
return true;
}
return $result;
} catch (Exception $e) {
if ($this->debug) {
$this->logger->debug("Error running check '$name':" . $e->getMessage() . ". Access denied.");
}
return false;
}
}
}

View File

@@ -255,7 +255,7 @@ class OrganisationController extends SimpleController
// 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 && strtolower($data['name']) != strtolower($organisation->name) &&
$classMapper->getClassMapping('organisation')::findUnique($data['name'], 'name') $classMapper->getClassMapping('organisation')::findUnique($data['name'], 'name')
) { ) {
$ms->addMessageTranslated('danger', 'ORGANISATION.NAME.IN_USE', $data); $ms->addMessageTranslated('danger', 'ORGANISATION.NAME.IN_USE', $data);
@@ -1103,6 +1103,9 @@ 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\Support\Repository\Repository $config */
$config = $this->ci->config;
/** @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;
@@ -1111,7 +1114,33 @@ class OrganisationController extends SimpleController
throw new ForbiddenException(); throw new ForbiddenException();
} }
return $this->ci->view->render($response, 'pages/organisations.html.twig'); $tableColumns = [ 'description', 'join' ];
if ($currentUser->organisations(true)->wherePivot('flag_admin', true)
|| $authorizer->checkAccess($currentUser, 'delete_organisation')
|| $authorizer->checkAccess($currentUser, 'update_organisation_field')
|| $authorizer->checkAccess($currentUser, 'approve_organisation')
|| $authorizer->checkAccess($currentUser, 'merge_organisations')
|| $config['organisation']['combine_action_buttons']) {
$tableColumns[] = 'status';
$tableColumns[] = 'actions';
}
if ($currentUser->organisations(true)->wherePivot('flag_admin', true)) {
$tableColumns[] = 'status';
$tableColumns[] = 'actions';
}
return $this->ci->view->render(
$response,
'pages/organisations.html.twig',
[
'table' => [
'columns' => $tableColumns
]
]
);
} }
/** /**
@@ -1142,7 +1171,23 @@ class OrganisationController extends SimpleController
throw new ForbiddenException(); throw new ForbiddenException();
} }
return $this->ci->view->render($response, 'pages/deleted-organisations.html.twig'); $tableColumns = [ 'description' ];
if ($authorizer->checkAccess($currentUser, 'restore_organisation') |
$authorizer->checkAccess($currentUser, 'permanently_delete_organisation')) {
$tableColumns[] = 'status';
$tableColumns[] = 'actions';
}
return $this->ci->view->render(
$response,
'pages/deleted-organisations.html.twig',
[
'table' => [
'columns' => $tableColumns
]
]
);
} }

View File

@@ -9,6 +9,7 @@
namespace UserFrosting\Sprinkle\Organisations\Controller; namespace UserFrosting\Sprinkle\Organisations\Controller;
use Carbon\Carbon;
use Illuminate\Database\Capsule\Manager as Capsule; use Illuminate\Database\Capsule\Manager as Capsule;
use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ServerRequestInterface as Request;
@@ -918,6 +919,81 @@ class OrganisationMembersController extends SimpleController
return $response->withRedirect($this->ci->router->pathFor('uri_organisation', ['slug' => $organisation->slug])); return $response->withRedirect($this->ci->router->pathFor('uri_organisation', ['slug' => $organisation->slug]));
} }
/**
* Processes the request to send a user a password reset email.
*
* Processes the request from the user update form, checking that:
* 1. The target user's new email address, if specified, is not already in use;
* 2. The logged-in user has the necessary permissions to update the posted field(s);
* 3. We're not trying to disable the master account;
* 4. The submitted data is valid.
* This route requires authentication.
*
* Request type: POST
*
* @param Request $request
* @param Response $response
* @param string[] $args
*
* @throws NotFoundException If user is not found
* @throws ForbiddenException If user is not authorized to access page
*/
public function createPasswordReset(Request $request, Response $response, array $args)
{
/** @var \UserFrosting\Sprinkle\Account\Authorize\AuthorizationManager $authorizer */
$authorizer = $this->ci->authorizer;
/** @var \UserFrosting\Support\Repository\Repository $config */
$config = $this->ci->config;
/** @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 username from the URL
$user = $this->getUserFromParams($args);
if (!$user) {
throw new NotFoundException();
}
// Access-controlled resource - check that currentUser has permission to edit "password" for this user
if (!$authorizer->checkAccess($currentUser, 'update_user_field', [
'user' => $user,
'fields' => ['password'],
])) {
throw new ForbiddenException();
}
// Begin transaction - DB will be rolled back if an exception occurs
Capsule::transaction(function () use ($user, $config) {
// Create a password reset and shoot off an email
$passwordReset = $this->ci->repoPasswordReset->create($user, $config['password_reset.timeouts.reset']);
// Create and send welcome email with password set link
$message = new TwigMailMessage($this->ci->view, 'mail/password-reset.html.twig');
$message->from($config['address_book.admin'])
->addEmailRecipient(new EmailRecipient($user->email, $user->full_name))
->addParams([
'user' => $user,
'token' => $passwordReset->getToken(),
'request_date' => Carbon::now()->format('Y-m-d H:i:s'),
]);
$this->ci->mailer->send($message);
});
$ms->addMessageTranslated('success', 'ORGANISATION.MEMBER.PASSWORD_LINK_SENT', [
'email' => $user->email,
]);
return $response->withJson([], 200);
}
/** /**
* Returns a list of organisation members. * Returns a list of organisation members.
@@ -1359,6 +1435,60 @@ class OrganisationMembersController extends SimpleController
]); ]);
} }
/**
* Renders the modal form for sending a password reset to a member.
*
* This does NOT render a complete page. Instead, it renders the HTML for the form, which can be embedded in other pages.
* This page requires authentication.
*
* Request type: GET
*
* @param Request $request
* @param Response $response
* @param string[] $args
*
* @throws NotFoundException If user is not found
* @throws ForbiddenException If user is not authorized to access page
*/
public function getModalResetPassword(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;
/** @var \UserFrosting\Support\Repository\Repository $config */
$config = $this->ci->config;
// GET parameters
$params = $request->getQueryParams();
$user = $this->getUserFromParams($params);
$organisation = $this->getOrganisationFromParams($params);
// Check organisation & user exists
if (!$organisation || !$user) {
throw new NotFoundException();
}
// Access-controlled resource - check that currentUser has permission to edit "password" field for this user
if (!$authorizer->checkAccess($currentUser, 'update_user_field', [
'organisation' => $organisation,
'user' => $user,
'fields' => ['password'],
])) {
throw new ForbiddenException();
}
return $this->ci->view->render($response, 'modals/member-reset-password.html.twig', [
'user' => $user,
'organisation' => $organisation,
'page' => [],
]);
}
/** /**
* Send approval email for specified organisation and confirmation to user. * Send approval email for specified organisation and confirmation to user.
@@ -1370,6 +1500,20 @@ class OrganisationMembersController extends SimpleController
{ {
$timeout = $this->ci->config['organisation.membership.timeout']; $timeout = $this->ci->config['organisation.membership.timeout'];
$admin_fallback = false;
$recipientsQuery = $organisation->administrators();
if ($recipientsQuery->count() == 0) {
$admin_fallback = true;
$role = $this->ci->classMapper->getClassMapping('role')::where('slug', 'organisations-admin')->with('users')->first();
if ($role->users()->count() == 0) {
$role = $this->ci->classMapper->getClassMapping('role')::where('slug', 'site-admin')->with('users')->first();
}
$recipientsQuery = $role->users();
}
$recipients = $recipientsQuery->get();
// Create and send approval email // Create and send approval email
$message = new TwigMailMessage($this->ci->view, 'mail/organisation-membership-request.html.twig'); $message = new TwigMailMessage($this->ci->view, 'mail/organisation-membership-request.html.twig');
@@ -1379,20 +1523,9 @@ class OrganisationMembersController extends SimpleController
'organisation' => $organisation, 'organisation' => $organisation,
'token' => $token, 'token' => $token,
'approval_expiration' => ($timeout > 0 ? floor($timeout / 86400) . ' days' : false), 'approval_expiration' => ($timeout > 0 ? floor($timeout / 86400) . ' days' : false),
'admin_fallback' => $admin_fallback,
]); ]);
$recipientsQuery = $organisation->administrators();
if ($recipientsQuery->count() == 0) {
$role = $this->ci->classMapper->getClassMapping('role')::where('slug', 'organisations-admin')->with('users')->first();
if ($role->users()->count() == 0) {
$role = $this->ci->classMapper->getClassMapping('role')::where('slug', 'site-admin')->with('users')->first();
}
$recipientsQuery = $role->users();
}
$recipients = $recipientsQuery->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 ]);

View File

@@ -7,7 +7,7 @@
* @license https://git.avsdev.uk/avsdev/sprinkle-organisations/blob/master/LICENSE.md (LGPL-3.0 License) * @license https://git.avsdev.uk/avsdev/sprinkle-organisations/blob/master/LICENSE.md (LGPL-3.0 License)
*/ */
namespace UserFrosting\Sprinkle\Organisations\Database\Migrations\v010; namespace UserFrosting\Sprinkle\Organisations\Database\Migrations\v1_0_0;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use UserFrosting\Sprinkle\Core\Database\Migration; use UserFrosting\Sprinkle\Core\Database\Migration;
@@ -25,7 +25,7 @@ class OrganisationApprovalsTable extends Migration
* {@inheritdoc} * {@inheritdoc}
*/ */
public static $dependencies = [ public static $dependencies = [
'\UserFrosting\Sprinkle\Account\Database\Migrations\v400\UsersTable', '\UserFrosting\Sprinkle\Account\Database\Migrations\v430\UpdateUsersTable',
]; ];
/** /**

View File

@@ -7,7 +7,7 @@
* @license https://git.avsdev.uk/avsdev/sprinkle-organisations/blob/master/LICENSE.md (LGPL-3.0 License) * @license https://git.avsdev.uk/avsdev/sprinkle-organisations/blob/master/LICENSE.md (LGPL-3.0 License)
*/ */
namespace UserFrosting\Sprinkle\Organisations\Database\Migrations\v010; namespace UserFrosting\Sprinkle\Organisations\Database\Migrations\v1_0_0;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use UserFrosting\Sprinkle\Organisations\Database\Models\Organisation; use UserFrosting\Sprinkle\Organisations\Database\Models\Organisation;
@@ -29,8 +29,8 @@ class OrganisationMembersTable extends Migration
* {@inheritdoc} * {@inheritdoc}
*/ */
public static $dependencies = [ public static $dependencies = [
'\UserFrosting\Sprinkle\Account\Database\Migrations\v400\UsersTable', '\UserFrosting\Sprinkle\Account\Database\Migrations\v430\UpdateUsersTable',
'\UserFrosting\Sprinkle\Organisations\Database\Migrations\v010\OrganisationsTable', '\UserFrosting\Sprinkle\Organisations\Database\Migrations\v1_0_0\OrganisationsTable',
]; ];
/** /**

View File

@@ -7,7 +7,7 @@
* @license https://git.avsdev.uk/avsdev/sprinkle-organisations/blob/master/LICENSE.md (LGPL-3.0 License) * @license https://git.avsdev.uk/avsdev/sprinkle-organisations/blob/master/LICENSE.md (LGPL-3.0 License)
*/ */
namespace UserFrosting\Sprinkle\Organisations\Database\Migrations\v010; namespace UserFrosting\Sprinkle\Organisations\Database\Migrations\v1_0_0;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use UserFrosting\Sprinkle\Core\Database\Migration; use UserFrosting\Sprinkle\Core\Database\Migration;
@@ -25,7 +25,7 @@ class OrganisationMembershipApprovalsTable extends Migration
* {@inheritdoc} * {@inheritdoc}
*/ */
public static $dependencies = [ public static $dependencies = [
'\UserFrosting\Sprinkle\Account\Database\Migrations\v400\UsersTable', '\UserFrosting\Sprinkle\Account\Database\Migrations\v430\UpdateUsersTable',
]; ];
/** /**

View File

@@ -7,7 +7,7 @@
* @license https://git.avsdev.uk/avsdev/sprinkle-organisations/blob/master/LICENSE.md (LGPL-3.0 License) * @license https://git.avsdev.uk/avsdev/sprinkle-organisations/blob/master/LICENSE.md (LGPL-3.0 License)
*/ */
namespace UserFrosting\Sprinkle\Organisations\Database\Migrations\v010; namespace UserFrosting\Sprinkle\Organisations\Database\Migrations\v1_0_0;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use UserFrosting\Sprinkle\Organisations\Database\Models\Organisation; use UserFrosting\Sprinkle\Organisations\Database\Models\Organisation;
@@ -27,7 +27,7 @@ class OrganisationsTable extends Migration
* {@inheritdoc} * {@inheritdoc}
*/ */
public static $dependencies = [ public static $dependencies = [
'\UserFrosting\Sprinkle\Account\Database\Migrations\v400\UsersTable', '\UserFrosting\Sprinkle\Account\Database\Migrations\v430\UpdateUsersTable',
]; ];
/** /**

View File

@@ -124,7 +124,7 @@ trait UserOrganisations {
} }
public function bootUserOrganisations() static public function bootUserOrganisations()
{ {
static::deleting(function ($user) { static::deleting(function ($user) {
if (!$user->deleteUserOrganisations($user->isForceDeleting())) { if (!$user->deleteUserOrganisations($user->isForceDeleting())) {

View File

@@ -13,7 +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; use UserFrosting\Sprinkle\UFTweaks\Repository\Interfaces\TokenOwnerInterface;
/** /**
* Organisation Class. * Organisation Class.

View File

@@ -12,7 +12,7 @@ namespace UserFrosting\Sprinkle\Organisations\Database\Models;
use Illuminate\Database\Eloquent\SoftDeletes; 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\Repository\Interfaces\TokenOwnerInterface; use UserFrosting\Sprinkle\UFTweaks\Repository\Interfaces\TokenOwnerInterface;
/** /**
* OrganisationMember mapping Class. * OrganisationMember mapping Class.

View File

@@ -43,6 +43,18 @@ class OrganisationPermissions extends BaseSeed
*/ */
protected function getPermissions() protected function getPermissions()
{ {
$roleIds = [
'user' => Role::where('slug', 'user')->first()->id,
'group-admin' => Role::where('slug', 'group-admin')->first()->id,
'site-admin' => Role::where('slug', 'site-admin')->first()->id,
'organisations-admin' => Role::where('slug', 'organisations-admin')->first()->id,
];
$canAdminOrgUsers = "(has_role(self.id,{$roleIds['organisations-admin']}) || (has_matching_organisation(self.id,user.id,1) && !has_role(user.id,{$roleIds['organisations-admin']})))";
$canAdminOrgAdmins = "(has_role(self.id,{$roleIds['organisations-admin']}) || has_matching_organisation(self.id,user.id,1))";
$excludeMasters = "(!is_master(user.id) && !has_role(user.id,{$roleIds['site-admin']}))";
$isSelf = "equals_num(self.id,user.id)";
return [ return [
'create_organisation' => new Permission([ 'create_organisation' => new Permission([
'slug' => 'create_organisation', 'slug' => 'create_organisation',
@@ -56,24 +68,19 @@ class OrganisationPermissions extends BaseSeed
'conditions' => "in(property,['name','slug','description'])", 'conditions' => "in(property,['name','slug','description'])",
'description' => 'View certain properties of any organisation.', 'description' => 'View certain properties of any organisation.',
]), ]),
'view_organisation_members_field' => new Permission([
'slug' => 'view_organisation_field',
'name' => 'View organisation members field',
'conditions' => "in(property,['members'])",
'description' => 'View members field of any organisation.',
]),
'view_organisation_members' => new Permission([
'slug' => 'view_organisation_members',
'name' => 'View organisation members',
'conditions' => "always()",
'description' => 'View members of any organisation.',
]),
'update_organisation_field' => new Permission([ 'update_organisation_field' => new Permission([
'slug' => 'update_organisation_field', 'slug' => 'update_organisation_field',
'name' => 'Edit organisation', 'name' => 'Edit organisation',
'conditions' => 'always()', 'conditions' => 'always()',
'description' => 'Edit basic properties of any organisation.', 'description' => 'Edit basic properties of any organisation.',
]), ]),
'delete_organisation' => new Permission([
'slug' => 'delete_organisation',
'name' => 'Delete organisation',
'conditions' => 'always()',
'description' => 'Delete an organisation.',
]),
'approve_organisation' => new Permission([ 'approve_organisation' => new Permission([
'slug' => 'approve_organisation', 'slug' => 'approve_organisation',
'name' => 'Approve/Deny organisation registration', 'name' => 'Approve/Deny organisation registration',
@@ -86,12 +93,6 @@ class OrganisationPermissions extends BaseSeed
'conditions' => 'always()', 'conditions' => 'always()',
'description' => 'Merge two organisations together, including all the members.', 'description' => 'Merge two organisations together, including all the members.',
]), ]),
'delete_organisation' => new Permission([
'slug' => 'delete_organisation',
'name' => 'Delete organisation',
'conditions' => 'always()',
'description' => 'Delete an organisation.',
]),
'restore_organisation' => new Permission([ 'restore_organisation' => new Permission([
'slug' => 'restore_organisation', 'slug' => 'restore_organisation',
'name' => 'Restore organisation', 'name' => 'Restore organisation',
@@ -111,6 +112,40 @@ class OrganisationPermissions extends BaseSeed
'description' => 'Accept/Reject organisation join requests.', 'description' => 'Accept/Reject organisation join requests.',
]), ]),
'uri_organisation' => new Permission([
'slug' => 'uri_organisation',
'name' => 'View organisation',
'conditions' => 'always()',
'description' => 'View the organisation page of any organisation.',
]),
'uri_organisations' => new Permission([
'slug' => 'uri_organisations',
'name' => 'Organisation management page',
'conditions' => 'always()',
'description' => 'View a page containing a list of organisations.',
]),
'uri_deleted_organisations' => new Permission([
'slug' => 'uri_deleted_organisations',
'name' => 'Deleted organisation management page',
'conditions' => 'always()',
'description' => 'View a page containing a list of deleted organisations.',
]),
'view_organisation_members' => new Permission([
'slug' => 'view_organisation_field',
'name' => 'View organisation members',
'conditions' => "in(property,['members'])",
'description' => 'View members of any organisation.',
]),
'promote_organisation_member' => new Permission([
'slug' => 'promote_organisation_member',
'name' => 'Promote organisation member/Demote organisation administrator',
'conditions' => "is_organisation_member(user.id,organisation.id)",
'description' => 'Promote an organisation member to administrator status or demote an administrator to member status.',
]),
'register_organisation' => new Permission([ 'register_organisation' => new Permission([
'slug' => 'register_organisation', 'slug' => 'register_organisation',
'name' => 'Register organisation', 'name' => 'Register organisation',
@@ -129,6 +164,15 @@ class OrganisationPermissions extends BaseSeed
'conditions' => 'always()', 'conditions' => 'always()',
'description' => 'Allows members to leave organisations.', 'description' => 'Allows members to leave organisations.',
]), ]),
'uri_organisation_own' => new Permission([
'slug' => 'uri_organisation',
'name' => 'View own organisation',
'conditions' => 'is_organisation_member(self.id,organisation.id)',
'description' => 'View the organisation page of an organisation you are a member of.',
]),
'view_organisation_field_own' => new Permission([ 'view_organisation_field_own' => new Permission([
'slug' => 'view_organisation_field', 'slug' => 'view_organisation_field',
'name' => 'View own organisation', 'name' => 'View own organisation',
@@ -147,70 +191,66 @@ class OrganisationPermissions extends BaseSeed
'conditions' => "is_organisation_admin(self.id,organisation.id)", 'conditions' => "is_organisation_admin(self.id,organisation.id)",
'description' => 'Accept/Reject organisation join requests.', 'description' => 'Accept/Reject organisation join requests.',
]), ]),
'uri_organisation' => new Permission([
'slug' => 'uri_organisation',
'name' => 'View organisation',
'conditions' => 'always()',
'description' => 'View the organisation page of any organisation.',
]),
'uri_organisation_own' => new Permission([
'slug' => 'uri_organisation',
'name' => 'View own organisation',
'conditions' => 'is_organisation_member(self.id,organisation.id)',
'description' => 'View the organisation page of an organisation you are a member of.',
]),
'uri_organisations' => new Permission([
'slug' => 'uri_organisations',
'name' => 'Organisation management page',
'conditions' => 'always()',
'description' => 'View a page containing a list of organisations.',
]),
'uri_deleted_organisations' => new Permission([
'slug' => 'uri_deleted_organisations',
'name' => 'Deleted organisation management page',
'conditions' => 'always()',
'description' => 'View a page containing a list of deleted organisations.',
]),
'update_org_user_field' => new Permission([
'slug' => 'update_user_field',
'name' => 'Edit organisation member',
'conditions' => "subset(fields,['organisations'])",
'description' => 'Edit users who are in any organisation.',
]),
'view_org_user_field' => new Permission([
'slug' => 'view_user_field',
'name' => 'View organisation member',
'conditions' => "in(property,['organisations'])",
'description' => 'View certain properties of any user in any organisation.',
]),
'update_org_user_field_own' => new Permission([
'slug' => 'update_user_field',
'name' => 'Edit organisation member',
'conditions' => "can_admin_via_orgs(self.id, user.id) && subset(fields,['organisations'])",
'description' => 'Edit users who are in an organisation they are a member of.',
]),
'view_org_user_field_own' => new Permission([
'slug' => 'view_user_field',
'name' => 'View organisation member',
'conditions' => "similar_orgs(self.id, user.id) && in(property,['organisations'])",
'description' => 'View certain properties of any user in their organisation.',
]),
'promote_organisation_member' => new Permission([
'slug' => 'promote_organisation_member',
'name' => 'Promote organisation member/Demote organisation administrator',
'conditions' => "is_organisation_member(user.id,organisation.id) || is_organisation_admin(user.id,organisation.id)",
'description' => 'Promote an organisation member to administrator status or demote and administrator to member status.',
]),
'promote_organisation_member_own' => new Permission([ 'promote_organisation_member_own' => new Permission([
'slug' => 'promote_organisation_member', 'slug' => 'promote_organisation_member',
'name' => 'Promote organisation member/Demote organisation administrator', 'name' => 'Promote organisation member/Demote organisation administrator',
'conditions' => "is_organisation_admin(self.id,organisation.id) && (is_organisation_member(user.id,organisation.id) || is_organisation_admin(user.id,organisation.id))", 'conditions' => "is_organisation_admin(self.id,organisation.id) && is_organisation_member(user.id,organisation.id)",
'description' => 'Promote an organisation member from your own organisation to administrator status or demote and administrator to member status.', 'description' => 'Promote an organisation member from your own organisation to administrator status or demote and administrator to member status.',
]), ]),
'uri_user_in_organisation' => new Permission([
'slug' => 'uri_user',
'name' => 'View user',
'conditions' => "(($canAdminOrgAdmins && $excludeMasters) || $isSelf)",
'description' => 'View the user page of any user in your orgnisation, except the master user and Site and (global) Organisation Administrators (except yourself).',
]),
'view_user_field' => new Permission([
'slug' => 'view_user_field',
'name' => 'View user',
'conditions' => "in(property,['organisations'])",
'description' => 'View the organisations property of any user.',
]),
'update_user_field' => new Permission([
'slug' => 'update_user_field',
'name' => 'Edit user',
'conditions' => "$excludeMasters && subset(fields,['organisations'])",
'description' => 'Edit organisations for users who are not Site Administrators.',
]),
'view_user_field_group' => new Permission([
'slug' => 'view_user_field',
'name' => 'View user',
'conditions' => "equals_num(self.group_id,user.group_id) && $excludeMasters && (!has_role(user.id,{$roleIds['group-admin']}) || equals_num(self.id,user.id)) && in(property,['organisations'])",
'description' => 'View organisations of any user in your own group, except the master user and Site and Group Administrators (except yourself).',
]),
'update_user_field_group' => new Permission([
'slug' => 'update_user_field',
'name' => 'Edit group user',
'conditions' => "equals_num(self.group_id,user.group_id) && $excludeMasters && (!has_role(user.id,{$roleIds['group-admin']}) || equals_num(self.id,user.id)) && subset(fields,['organisations'])",
'description' => 'Edit organisations for users in your own group who are not Site or Group Administrators, except yourself.',
]),
'view_user_field_organisation_audit' => new Permission([
'slug' => 'view_user_field',
'name' => 'View user',
'conditions' => "(($canAdminOrgUsers && $excludeMasters) || $isSelf) && in(property,['activities'])",
'description' => 'View certain properties of any user in your own organisation, except the master user and Site and (global) Organisation Administrators (except yourself).',
]),
'update_user_field_organisation' => new Permission([
'slug' => 'update_user_field',
'name' => 'Edit organisation user',
'conditions' => "(($canAdminOrgUsers && $excludeMasters) || $isSelf) && subset(fields,['name','email','locale','flag_enabled','flag_verified','password'])",
'description' => 'Edit users in your own organisation who are not Site or (global) Organisation Administrators, except yourself.',
]),
'view_user_field_organisation' => new Permission([
'slug' => 'view_user_field',
'name' => 'View user',
'conditions' => "(($canAdminOrgUsers && $excludeMasters) || $isSelf) && in(property,['user_name','name','email','locale','roles','group','organisations'])",
'description' => 'View certain properties of any user in your own organisation, except the master user and Site and (global) Organisation Administrators (except yourself).',
]),
]; ];
} }
@@ -247,44 +287,43 @@ class OrganisationPermissions extends BaseSeed
$roleSiteAdmin = Role::where('slug', 'site-admin')->first(); $roleSiteAdmin = Role::where('slug', 'site-admin')->first();
if ($roleSiteAdmin) { if ($roleSiteAdmin) {
$roleSiteAdmin->permissions()->syncWithoutDetaching([ $roleSiteAdmin->permissions()->syncWithoutDetaching([
$permissions['view_user_field']->id,
$permissions['update_user_field']->id,
$permissions['create_organisation']->id, $permissions['create_organisation']->id,
$permissions['approve_organisation']->id,
$permissions['view_organisation_field']->id, $permissions['view_organisation_field']->id,
$permissions['view_organisation_members_field']->id,
$permissions['view_organisation_members']->id,
$permissions['update_organisation_field']->id, $permissions['update_organisation_field']->id,
$permissions['merge_organisations']->id,
$permissions['delete_organisation']->id, $permissions['delete_organisation']->id,
$permissions['approve_organisation']->id,
$permissions['merge_organisations']->id,
$permissions['restore_organisation']->id, $permissions['restore_organisation']->id,
$permissions['permenent_delete_organisation']->id, $permissions['permenent_delete_organisation']->id,
$permissions['accept_organisation_join_request']->id,
$permissions['uri_organisation']->id, $permissions['uri_organisation']->id,
$permissions['uri_organisations']->id,
$permissions['uri_deleted_organisations']->id, $permissions['uri_deleted_organisations']->id,
$permissions['view_organisation_members']->id,
$permissions['register_organisation']->id,
$permissions['join_organisation']->id,
$permissions['leave_organisation']->id,
$permissions['view_org_user_field_own']->id,
$permissions['update_org_user_field_own']->id,
$permissions['accept_organisation_join_request_own']->id,
$permissions['promote_organisation_member_own']->id,
$permissions['view_organisation_field_own']->id,
$permissions['update_organisation_field_own']->id,
$permissions['accept_organisation_join_request']->id,
$permissions['update_org_user_field']->id,
$permissions['view_org_user_field']->id,
$permissions['promote_organisation_member']->id, $permissions['promote_organisation_member']->id,
]);
}
$permissions['uri_organisation_own']->id, $roleUserAdmin = Role::where('slug', 'user-admin')->first();
$permissions['uri_organisations']->id, if ($roleSiteAdmin) {
$roleSiteAdmin->permissions()->syncWithoutDetaching([
$permissions['view_user_field']->id,
$permissions['update_user_field']->id,
]);
}
$roleGroupAdmin = Role::where('slug', 'group-admin')->first();
if ($roleGroupAdmin) {
$roleGroupAdmin->permissions()->sync([
$permissions['view_user_field_group']->id,
$permissions['update_user_field_group']->id,
]); ]);
} }
@@ -292,58 +331,27 @@ class OrganisationPermissions extends BaseSeed
if ($roleOrgAdmin) { if ($roleOrgAdmin) {
$roleOrgAdmin->permissions()->syncWithoutDetaching([ $roleOrgAdmin->permissions()->syncWithoutDetaching([
$permissions['create_organisation']->id, $permissions['create_organisation']->id,
$permissions['approve_organisation']->id,
$permissions['view_organisation_field']->id, $permissions['view_organisation_field']->id,
$permissions['view_organisation_members_field']->id,
$permissions['view_organisation_members']->id,
$permissions['update_organisation_field']->id, $permissions['update_organisation_field']->id,
$permissions['merge_organisations']->id,
$permissions['delete_organisation']->id, $permissions['delete_organisation']->id,
$permissions['approve_organisation']->id,
$permissions['merge_organisations']->id,
$permissions['restore_organisation']->id, $permissions['restore_organisation']->id,
$permissions['permenent_delete_organisation']->id, $permissions['permenent_delete_organisation']->id,
$permissions['accept_organisation_join_request']->id,
$permissions['uri_organisation']->id, $permissions['uri_organisation']->id,
$permissions['uri_organisations']->id,
$permissions['uri_user_in_organisation']->id,
$permissions['uri_deleted_organisations']->id, $permissions['uri_deleted_organisations']->id,
$permissions['view_organisation_members']->id,
$permissions['register_organisation']->id,
$permissions['join_organisation']->id,
$permissions['leave_organisation']->id,
$permissions['view_org_user_field_own']->id,
$permissions['update_org_user_field_own']->id,
$permissions['accept_organisation_join_request_own']->id,
$permissions['promote_organisation_member_own']->id,
$permissions['view_organisation_field_own']->id,
$permissions['update_organisation_field_own']->id,
$permissions['accept_organisation_join_request']->id,
$permissions['update_org_user_field']->id,
$permissions['view_org_user_field']->id,
$permissions['promote_organisation_member']->id, $permissions['promote_organisation_member']->id,
$permissions['uri_organisation_own']->id, $permissions['view_user_field_organisation_audit']->id,
$permissions['uri_organisations']->id, $permissions['view_user_field_organisation']->id,
$permissions['update_user_field_organisation']->id,
Permission::where('slug', 'create_user')->first()->id,
Permission::where('slug', 'delete_user')->first()->id,
Permission::where('slug', 'update_user_field')->where('conditions', "!has_role(user.id,{$roleSiteAdmin->id}) && subset(fields,['name','email','locale','group','flag_enabled','flag_verified','password'])")->first()->id,
Permission::where('slug', 'uri_users')->first()->id,
Permission::where('slug', 'uri_user')->where('conditions', 'always()')->first()->id,
]);
}
$roleAuditer = Role::where('slug', 'auditer')->first();
if ($roleAuditer) {
$roleAuditer->permissions()->syncWithoutDetaching([
Permission::where('slug', 'uri_activities')->first()->id,
]); ]);
} }
@@ -354,17 +362,17 @@ class OrganisationPermissions extends BaseSeed
$permissions['join_organisation']->id, $permissions['join_organisation']->id,
$permissions['leave_organisation']->id, $permissions['leave_organisation']->id,
$permissions['view_org_user_field_own']->id, $permissions['view_organisation_field_own']->id,
$permissions['update_org_user_field_own']->id, $permissions['update_organisation_field_own']->id,
$permissions['accept_organisation_join_request_own']->id, $permissions['accept_organisation_join_request_own']->id,
$permissions['promote_organisation_member_own']->id, $permissions['promote_organisation_member_own']->id,
$permissions['view_organisation_field_own']->id,
$permissions['update_organisation_field_own']->id,
$permissions['uri_organisation_own']->id, $permissions['uri_organisation_own']->id,
$permissions['uri_organisations']->id, $permissions['uri_organisations']->id,
$permissions['uri_user_in_organisation']->id,
$permissions['view_user_field_organisation']->id,
$permissions['update_user_field_organisation']->id,
]); ]);
} }
} }

View File

@@ -41,12 +41,7 @@ class OrganisationRoles extends BaseSeed
new Role([ new Role([
'slug' => 'organisations-admin', 'slug' => 'organisations-admin',
'name' => 'Organisations Administrator', 'name' => 'Organisations Administrator',
'description' => 'This role is meant for "organisation administrators", who can basically do anything related to organisations and their members.', 'description' => 'This role is meant for administrators who can basically do anything related to any organisations.',
]),
new Role([
'slug' => 'auditer',
'name' => 'Audit Viewer',
'description' => 'This role is meant for "auditers", who are allowed to view the activity log.',
]), ]),
]; ];
} }

View File

@@ -1,492 +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\Repository;
use Carbon\Carbon;
use UserFrosting\Sprinkle\Organisations\Repository\Interfaces\TokenOwnerInterface;
use UserFrosting\Sprinkle\Core\Database\Models\Model;
use UserFrosting\Sprinkle\Core\Util\ClassMapper;
/**
* An abstract class for interacting with a repository of time-sensitive tokens which can be owned by any object.
*
* Tokens are used, for example, to perform password resets and new account email verifications.
*
* @author Craig Williams (https://avsdev.uk)
*/
abstract class BasicTokenRepository
{
/**
* @var ClassMapper
*/
protected $classMapper;
/**
* @var string
*/
protected $algorithm;
/**
* @var string
*/
protected $modelIdentifier;
/**
* @var TokenLogger
*/
protected $tokenLogger;
/**
* Create a new TokenRepository object.
*
* @param ClassMapper $classMapper Maps generic class identifiers to specific class names.
* @param string $algorithm The hashing algorithm to use when storing generated tokens.
*/
public function __construct(ClassMapper $classMapper, $algorithm = 'sha512', $tokenLogger = null, $debug = false)
{
$this->classMapper = $classMapper;
$this->algorithm = $algorithm;
$this->tokenLogger = ($debug ? $tokenLogger : null);
}
/**
* Create a new token.
*
* @param TokenOwnerInterface $tokenOwner The object to associate with this token, be it an organisation, user, group etc.
* @param int $timeout The time, in seconds, after which this token should expire.
*
* @return Model The model (PasswordReset, Verification, etc) object that stores the token.
*/
public function create(TokenOwnerInterface $tokenOwner, $timeout)
{
// Remove any previous tokens for this tokenOwner
$this->removeExisting($tokenOwner);
if ($this->tokenLogger) {
$this->tokenLogger->debug('Creating new token for {{owner}} which expires in {{timeout}} seconds', [
'tokenOwner' => $tokenOwner,
'timeout' => $timeout
]);
}
// 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([
'owner_id' => $tokenOwner->getId(),
'hash' => $hash,
'completed' => false,
'expires_at' => ($timeout >= 0 ? $expiresAt : null),
]);
$model->save();
if ($this->tokenLogger) {
$this->tokenLogger->debug('Completed new token', ['model' => $model]);
}
return $model;
}
/**
* Cancels a specified token by removing it from the database.
*
* @param int $token The token to remove.
*
* @return Model|false
*/
public function cancel($token)
{
if ($this->tokenLogger) {
$this->tokenLogger->debug('Cancelling token {{token}}', [
'token' => $token
]);
}
// Hash the password reset token for the stored version
$hash = hash($this->algorithm, $token);
// Find an incomplete reset request for the specified hash
$model = $this->classMapper->getClassMapping($this->modelIdentifier)::query()
->where('hash', $hash)
->where('completed', false)
->first();
if ($model === null) {
if ($this->tokenLogger) {
$this->tokenLogger->warn('Token not found!');
}
return false;
}
if ($this->tokenLogger) {
$this->tokenLogger->debug('Deleting matched model', [
'model' => $model
]);
}
$model->delete();
return $model;
}
/**
* Completes a token-based process, invoking updateTokenOwner() in the child object to do the actual action.
*
* @param int $token The token to complete.
* @param mixed[] $params An optional list of parameters to pass to updateUser().
*
* @return Model|false
*/
public function complete($token, $params = [])
{
if ($this->tokenLogger) {
$this->tokenLogger->debug('Completing token for {{token}}', [
'token' => $token
]);
}
// Hash the token for the stored version
$hash = hash($this->algorithm, $token);
// Find an unexpired, incomplete token for the specified hash
$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) {
if ($this->tokenLogger) {
$this->tokenLogger->warn('Token not found!');
}
return false;
}
if ($this->tokenLogger) {
$this->tokenLogger->debug('Found:', [
'model' => $model
]);
}
$ret = $this->updateTokenOwner($model->owner_id, $model, $params);
if ($this->tokenLogger) {
$this->tokenLogger->debug('Return of updateTokenOwner: {{ret}}', [
'ret' => $ret
]);
}
$model->fill([
'completed' => true,
'completed_at' => Carbon::now(),
]);
$model->save();
return $model;
}
/**
* Completes a token-based process using the owner instead of the token,
* invoking updateTokenOwner() in the child object to do the actual action.
*
* @param int $token The token to complete.
* @param mixed[] $params An optional list of parameters to pass to updateUser().
*
* @return Model|false
*/
public function completeForOwner(TokenOwnerInterface $tokenOwner, $params = [])
{
if ($this->tokenLogger) {
$this->tokenLogger->debug('Completing token for {{owner}}', [
'owner' => $tokenOwner
]);
}
// Find an unexpired, incomplete tokens for the specified owner.
// Using first() works because owners can only have at most 1 active token
$model = $this->classMapper->getClassMapping($this->modelIdentifier)::query()
->where('owner_id', $tokenOwner->getId())
->where('completed', false)
->where(function($query) {
return $query->where('expires_at', '>', Carbon::now())->orWhereNull('expires_at');
})
->first();
if ($model === null) {
if ($this->tokenLogger) {
$this->tokenLogger->warn('Token not found!');
}
return false;
}
if ($this->tokenLogger) {
$this->tokenLogger->debug('Found:', [
'model' => $model
]);
}
$ret = $this->updateTokenOwner($model->owner_id, $model, $params);
if ($this->tokenLogger) {
$this->tokenLogger->debug('Return of updateTokenOwner: {{ret}}', [
'ret' => $ret
]);
}
$model->fill([
'completed' => true,
'completed_at' => Carbon::now(),
]);
$model->save();
return $model;
}
/**
* Reverts a completed token request to its previously incomplete state.
*
* @param TokenOwnerInterface $tokenOwner The owner of the token to revert.
*
* @return Model|false
*/
public function revert(TokenOwnerInterface $tokenOwner)
{
if ($this->tokenLogger) {
$this->tokenLogger->debug('Reverting token for {{owner}}', [
'owner' => $tokenOwner
]);
}
// Find an unexpired, incomplete tokens for the specified owner.
// Using first() works because owners can only have at most 1 active token
$model = $this->classMapper->getClassMapping($this->modelIdentifier)::query()
->where('owner_id', $tokenOwner->getId())
->where('completed', true)
->where(function($query) {
return $query->where('expires_at', '>', Carbon::now())->orWhereNull('expires_at');
})
->first();
if ($model === null) {
if ($this->tokenLogger) {
$this->tokenLogger->warn('Token not found!');
}
return false;
}
if ($this->tokenLogger) {
$this->tokenLogger->debug('Found:', [
'model' => $model
]);
}
$model->fill([
'completed' => false,
'completed_at' => null,
]);
$model->save();
return $model;
}
/**
* Determine if a specified token owner has an incomplete and unexpired token.
*
* @param TokenOwnerInterface $tokenOwner The token owner object to look up.
* @param int $token Optionally, try to match a specific token.
*
* @return Model|false
*/
public function exists(TokenOwnerInterface $tokenOwner, $token = null)
{
if ($this->tokenLogger) {
$this->tokenLogger->debug('Searching for token for {{owner}} (with optional {{token}})', [
'owner' => $tokenOwner,
'token' => $token
]);
}
$model = $this->classMapper->getClassMapping($this->modelIdentifier)::query()
->where('owner_id', $tokenOwner->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);
if ($this->tokenLogger) {
$this->tokenLogger->debug('Token hash: {{hash}}', [
'hash' => $hash
]);
}
$model->where('hash', $hash);
}
$result = $model->first() ?: false;
if ($this->tokenLogger) {
$this->tokenLogger->debug('Found:', [
'result' => $result
]);
}
return $result;
}
/**
* Find an unexpired and un-completed token for the specified owner.
*
* @param TokenOwnerInterface $tokenOwner The token owner object to look up.
* @param int $token Optionally, try to match a specific token.
*
* @return owner_id|null
*/
public function findOwner($token)
{
if ($this->tokenLogger) {
$this->tokenLogger->debug('Searching token for owner of {{token}}', [
'token' => $token
]);
}
// get token hash
$hash = hash($this->algorithm, $token);
if ($this->tokenLogger) {
$this->tokenLogger->debug('Token hash: {{hash}}', [
'hash' => $hash
]);
}
$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 ($this->tokenLogger) {
$this->tokenLogger->debug('Found:', [
'model' => $model
]);
}
return $model ? $model->owner_id : null;
}
/**
* Delete all existing tokens from the database for a particular owner.
*
* @param TokenOwnerInterface $tokenOwner
*
* @return int
*/
public function removeExisting(TokenOwnerInterface $tokenOwner)
{
if ($this->tokenLogger) {
$this->tokenLogger->debug('Removing all tokens for {{owner}}', [
'owner' => $tokenOwner
]);
}
$result = $this->classMapper->getClassMapping($this->modelIdentifier)::query()
->where('owner_id', $tokenOwner->getId())
->delete();
if ($this->tokenLogger) {
$this->tokenLogger->debug('Deletion result: {{result}}', [
'result' => $result
]);
}
return $result;
}
/**
* Remove all expired tokens from the database.
*
* @return bool|null
*/
public function removeExpired()
{
if ($this->tokenLogger) {
$this->tokenLogger->debug('Removing expired tokens...');
}
$result = $this->classMapper->getClassMapping($this->modelIdentifier)::query()
->where('completed', false)
->whereNotNull('expires_at')
->where('expires_at', '<', Carbon::now())
->delete();
if ($this->tokenLogger) {
$this->tokenLogger->debug('Deletion result: {{result}}', [
'result' => $result
]);
}
return $result;
}
/**
* Generate a new random token.
*
* This generates a token to use for verifying anything you like, with a unique reference.
*
* @return string
*/
protected function generateRandomToken()
{
do {
$gen = md5(uniqid(mt_rand(), false));
} while ($this->classMapper->getClassMapping($this->modelIdentifier)::query()
->where('hash', hash($this->algorithm, $gen))
->first());
if ($this->tokenLogger) {
$this->tokenLogger->debug('Generated token {{token}}', [
'token' => $gen
]);
}
return $gen;
}
/**
* Modify the owner of the token during the token completion process.
*
* This method is called during complete(), and is a way for concrete implementations to modify the owner.
*
* @param integer $owner_id The id of the token owner
* @param mixed[] $args
*
* @return mixed[] $args the list of parameters that were supplied to the call to `complete()`
*/
abstract protected function updateTokenOwner($owner_id, $model, $args);
}

View File

@@ -1,25 +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\Repository\Interfaces;
/**
* Token Owner Interface.
*
* Represents a Token Owner object. The only requirement is that it must provide a unique id per object.
*/
interface TokenOwnerInterface
{
/**
* Returns a unique ID for this Token Owner
*
* @return integer|string
*/
public function getId();
}

View File

@@ -10,6 +10,7 @@
namespace UserFrosting\Sprinkle\Organisations\Repository; namespace UserFrosting\Sprinkle\Organisations\Repository;
use UserFrosting\Sprinkle\Core\Util\ClassMapper; use UserFrosting\Sprinkle\Core\Util\ClassMapper;
use UserFrosting\Sprinkle\UFTweaks\Repository\BasicTokenRepository;
/** /**
* Token repository class for new organisation approval. * Token repository class for new organisation approval.
@@ -28,7 +29,7 @@ class OrganisationApprovalRepository extends BasicTokenRepository
*/ */
protected function updateTokenOwner($owner_id, $model, $args) protected function updateTokenOwner($owner_id, $model, $args)
{ {
$organisation = $this->classMapper->getClassMapping('organisation')::findUnique($owner_id, 'id', false); $organisation = $this->classMapper->getClassMapping('organisation')::where('id', $owner_id)->first();
if (!$organisation) { if (!$organisation) {
return false; return false;

View File

@@ -10,6 +10,7 @@
namespace UserFrosting\Sprinkle\Organisations\Repository; namespace UserFrosting\Sprinkle\Organisations\Repository;
use UserFrosting\Sprinkle\Core\Util\ClassMapper; use UserFrosting\Sprinkle\Core\Util\ClassMapper;
use UserFrosting\Sprinkle\UFTweaks\Repository\BasicTokenRepository;
/** /**
* Token repository organisation membership join requests. * Token repository organisation membership join requests.
@@ -28,7 +29,7 @@ class OrganisationMembershipApprovalRepository extends BasicTokenRepository
*/ */
protected function updateTokenOwner($owner_id, $model, $args) protected function updateTokenOwner($owner_id, $model, $args)
{ {
$memberMap = $this->classMapper->getClassMapping('organisation_member')::findUnique($owner_id, 'map_id', false); $memberMap = $this->classMapper->getClassMapping('organisation_member')::where('map_id', $owner_id)->first();
if (!$memberMap) { if (!$memberMap) {
return false; return false;

View File

@@ -10,19 +10,14 @@
namespace UserFrosting\Sprinkle\Organisations\ServicesProvider; namespace UserFrosting\Sprinkle\Organisations\ServicesProvider;
use Illuminate\Database\Capsule\Manager as Capsule; use Illuminate\Database\Capsule\Manager as Capsule;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface as Response; use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request; use Psr\Http\Message\ServerRequestInterface as Request;
use UserFrosting\Sprinkle\Core\Log\MixedFormatter;
use UserFrosting\Sprinkle\Organisations\Database\Models\Interfaces\OrganisationInterface; use UserFrosting\Sprinkle\Organisations\Database\Models\Interfaces\OrganisationInterface;
use UserFrosting\Sprinkle\Organisations\Database\Models\User; use UserFrosting\Sprinkle\Organisations\Database\Models\User;
use UserFrosting\Sprinkle\Organisations\Twig\OrganisationsExtension; use UserFrosting\Sprinkle\Organisations\Twig\OrganisationsExtension;
use UserFrosting\Sprinkle\Organisations\Repository\OrganisationApprovalRepository; use UserFrosting\Sprinkle\Organisations\Repository\OrganisationApprovalRepository;
use UserFrosting\Sprinkle\Organisations\Repository\OrganisationMembershipApprovalRepository; use UserFrosting\Sprinkle\Organisations\Repository\OrganisationMembershipApprovalRepository;
use UserFrosting\Sprinkle\Organisations\Authorize\AuthorizationManager;
/** /**
@@ -64,8 +59,21 @@ class ServicesProvider
* @return \UserFrosting\Sprinkle\Core\Util\ClassMapper * @return \UserFrosting\Sprinkle\Core\Util\ClassMapper
*/ */
$container->extend('authorizer', function ($authorizer, $c) { $container->extend('authorizer', function ($authorizer, $c) {
/*
* Check if all $user has an $organisation.
*
* @param int $user_id the id of the requesting user (normally currentUser->id).
* @return bool true if $user is a verified member of any organisation.
*/
$authorizer->addCallback('has_organisation', function ($user_id) {
$query = Capsule::table('organisation_members')
->join('organisations', 'organisation_members.organisation_id', 'organisations.id')
->where('user_id', $user_id)
->where('organisation_members.flag_approved', true)
->where('organisations.flag_approved', true);
$new_authorizer = new AuthorizationManager($c, $authorizer->getCallbacks()); return $query->count() > 0;
});
/* /*
* Check if all $user is a member of $organisation. * Check if all $user is a member of $organisation.
@@ -74,12 +82,17 @@ class ServicesProvider
* @param int $organisation_id the id of the target organisation. * @param int $organisation_id the id of the target organisation.
* @return bool true if $user is a member of $organisation. * @return bool true if $user is a member of $organisation.
*/ */
$new_authorizer->addCallback('is_organisation_member', function ($user_id, $organisation_id) { $authorizer->addCallback('is_organisation_member', function ($user_id, $organisation_id, $explicit = false) {
return Capsule::table('organisation_members') $query = Capsule::table('organisation_members')
->where('user_id', $user_id) ->where('user_id', $user_id)
->where('organisation_id', $organisation_id) ->where('organisation_id', $organisation_id)
->where('flag_approved', true) ->where('flag_approved', true);
->count() > 0;
if ($explicit) {
$query = $query->where('flag_admin', false);
}
return $query->count() > 0;
}); });
/* /*
@@ -89,35 +102,45 @@ class ServicesProvider
* @param int $organisation_id the id of the target organisation. * @param int $organisation_id the id of the target organisation.
* @return bool true if $user is an administrator of $organisation. * @return bool true if $user is an administrator of $organisation.
*/ */
$new_authorizer->addCallback('is_organisation_admin', function ($user_id, $organisation_id) { $authorizer->addCallback('is_organisation_admin', function ($user_id, $organisation_id) {
return Capsule::table('organisation_members') $query = Capsule::table('organisation_members')
->where('user_id', $user_id) ->where('user_id', $user_id)
->where('organisation_id', $organisation_id) ->where('organisation_id', $organisation_id)
->where('flag_admin', true) ->where('flag_admin', true);
->count() > 0;
return $query->count() > 0;
}); });
/* /*
* Check if $admin_id can modify $user_id via any of their joint organisations * Check if $user_A_id is in an organisation that $user_B_id is also in
* *
* @param int $admin_id the id of the admin user (normally currentUser->id). * @param int $user_A_id the id of the first user (normally currentUser->id).
* @param int $user_id the id of the target user. * @param int $user_B_id the id of the second user.
* @return bool true if $admin_id is an administrator of an organisation with $user_id in. * @param bool $check_is_admin also check if A can administrate B.
* @return bool true if $user_A_id in an organisation with $user_B_id in.
*/ */
$new_authorizer->addCallback('can_admin_via_orgs', function ($admin_id, $user_id) { $authorizer->addCallback('has_matching_organisation', function ($user_A_id, $user_B_id, $check_is_admin = false) {
$admin = User::findInt($admin_id); $user_A = User::findInt($user_A_id);
$user = User::findInt($user_id); $user_B = User::findInt($user_B_id);
foreach($admin->adminForOrganisations()->get() as $org) { if ($check_is_admin) {
if ($org->members(true)->where('user_id', $user_id)->count() > 0) { foreach($user_A->adminForOrganisations()->get() as $org) {
if ($org->members(true)->where('user_id', $user_B_id)->count() > 0) {
return true; return true;
} }
} }
} else {
foreach($user_A->organisations()->get() as $org) {
if ($org->members(true)->where('user_id', $user_B_id)->count() > 0) {
return true;
}
}
}
return false; return false;
}); });
return $new_authorizer; return $authorizer;
}); });
/* /*
@@ -127,34 +150,14 @@ class ServicesProvider
*/ */
$container->extend('view', function ($view, $c) { $container->extend('view', function ($view, $c) {
$twig = $view->getEnvironment(); $twig = $view->getEnvironment();
$extension = new OrganisationsExtension($c); $extension = new OrganisationsExtension($c);
$twig->addExtension($extension); $twig->addExtension($extension);
return $view; return $view;
}); });
/*
* Token logging with Monolog.
*
* Extend this service to push additional handlers onto the 'tokens' log stack.
*
* @return \Monolog\Logger
*/
$container['tokenLogger'] = function ($c) {
$logger = new Logger('tokens');
$logFile = $c->locator->findResource('log://userfrosting.log', true, true);
$handler = new StreamHandler($logFile);
$formatter = new MixedFormatter(null, null, true);
$handler->setFormatter($formatter);
$logger->pushHandler($handler);
return $logger;
};
/* /*
* Returns a callback that handles merging any organisation objects. * Returns a callback that handles merging any organisation objects.
* *

View File

@@ -125,6 +125,31 @@ class OrganisationSprunje extends Sprunje
return $this; return $this;
} }
/**
* Filter by description (case insensitive).
*
* @param Builder $query
* @param mixed $value
*
* @return self
*/
protected function filterInfo($query, $value)
{
// Split value on separator for OR queries
$values = explode($this->orSeparator, $value);
$query->where(function ($query) use ($values) {
foreach ($values as $value) {
$query->orWhereRaw(
'LOWER(name) LIKE ?', [ '%' . strtolower($value) . '%' ]
)->orWhereRaw(
'LOWER(description) LIKE ?', [ '%' . strtolower($value) . '%' ]
);
}
});
return $this;
}
/** /**
* Return a list of possible user statuses. * Return a list of possible user statuses.
* *

View File

@@ -66,18 +66,14 @@ class OrganisationsExtension extends AbstractExtension implements GlobalsInterfa
return Capsule::table('organisation_members') return Capsule::table('organisation_members')
->where('user_id', $currentUser->id) ->where('user_id', $currentUser->id)
->where('organisation_id', $organisation->id) ->where('organisation_id', $organisation->id)
->where('flag_admin', true)
->where('flag_approved', true) ->where('flag_approved', true)
->where('flag_admin', true)
->count() > 0; ->count() > 0;
}), }),
new TwigFunction('isOrganisationRegistrant', function ($organisation) { new TwigFunction('isOrganisationRegistrant', function ($organisation) {
$currentUser = $this->services->currentUser; $currentUser = $this->services->currentUser;
return $organisation->registrant_id == $currentUser->id; return $organisation->registrant_id == $currentUser->id;
}), }),
new TwigFunction('hasRole', function ($roleSlug) {
$currentUser = $this->services->currentUser;
return $currentUser->roles()->where('slug', $roleSlug)->count() > 0;
}),
]; ];
} }

View File

@@ -0,0 +1,7 @@
{% include "@uf-tweaks/forms/inputs/abstract/description.html.twig" with
{
"type" : "organisation",
"current_value" : organisation.description,
"col_width" : col_width
}
%}

View File

@@ -0,0 +1,8 @@
{% include "@uf-tweaks/forms/inputs/abstract/name.html.twig" with
{
"type" : "organisation",
"current_value" : organisation.name,
"field_name" : translate('ORGANISATION.NAME'),
"col_width" : col_width
}
%}

View File

@@ -0,0 +1,7 @@
{% include "@uf-tweaks/forms/inputs/abstract/slug.html.twig" with
{
"type" : "organisation",
"current_value" : organisation.slug,
"col_width" : col_width
}
%}

View File

@@ -4,64 +4,26 @@
</div> </div>
<div class="row"> <div class="row">
{% block organisation_form %} {% block organisation_form %}
{% block organisation_field_name %} {% block input_organisation_name %}
{% if 'name' not in form.fields.hidden %} {% include "forms/inputs/organisation-name.html.twig" with { 'col_width': 'col-sm-12' } %}
<div class="col-sm-12">
<div class="form-group">
<label>{{translate("ORGANISATION.NAME")}}</label>
<div class="input-group">
<span class="input-group-addon"><i class="fas fa-edit fa-fw"></i></span>
<input type="text" class="form-control" name="name" autocomplete="off" value="{{organisation.name}}" placeholder="{{translate("ORGANISATION.NAME.EXPLAIN")}}" {% if 'name' in form.fields.disabled %}disabled{% endif %}>
</div>
</div>
</div>
{% endif %}
{% endblock %} {% endblock %}
{% block organisation_field_slug %} {% block input_organisation_slug %}
{% if 'slug' not in form.fields.hidden %} {% include "forms/inputs/organisation-slug.html.twig" with { 'col_width': 'col-sm-12' } %}
<div class="col-sm-12">
<div class="form-group">
<label>{{translate("SLUG")}}</label>
<div class="input-group">
<span class="input-group-addon"><i class="fas fa-tag fa-fw"></i></span>
<input type="text" class="form-control" name="slug" autocomplete="off" value="{{organisation.slug}}" placeholder="{{translate("SLUG")}}" {% if 'slug' in form.fields.disabled %}disabled{% endif %} readonly>
{% if 'slug' not in form.fields.disabled %}
<span class="input-group-btn" data-toggle="buttons">
<label class="btn btn-primary">
<input type="checkbox" id="form-organisation-slug-override" autocomplete="off"> {{translate("OVERRIDE")}}
</label>
</span>
{% endif %}
</div>
</div>
</div>
{% else %}
<input type="hidden" name="slug" autocomplete="off" value="{{organisation.slug}}">
{% endif %}
{% endblock %} {% endblock %}
{% block organisation_field_description %} {% block input_organisation_description %}
{% if 'description' not in form.fields.hidden %} {% include "forms/inputs/organisation-description.html.twig" with { 'col_width': 'col-sm-12' } %}
<div class="col-sm-12">
<div class="form-group">
<label for="input_description">{{translate("DESCRIPTION")}}</label>
<textarea id="input_description" class="form-control" type="text" name="description" {% if 'description' in form.fields.disabled %}disabled{% endif %} rows=6>{{organisation.description}}</textarea>
</div>
</div>
{% endif %}
{% endblock %} {% endblock %}
{% endblock %} {% endblock %}
</div><br> </div><br>
<div class="row"> <div class="row">
{% block organisation_form_buttons %}
<div class="col-xs-6 col-sm-4"> <div class="col-xs-6 col-sm-4">
<button type="submit" class="btn btn-block btn-lg btn-success">{{form.submit_text}}</button> <button type="submit" class="btn btn-block btn-lg btn-success">{{form.submit_text}}</button>
</div> </div>
<div class="col-xs-4 col-sm-3 pull-right"> <div class="col-xs-4 col-sm-3 pull-right">
<button type="button" class="btn btn-block btn-lg btn-link" data-dismiss="modal">{{translate("CANCEL")}}</button> <button type="button" class="btn btn-block btn-lg btn-link" data-dismiss="modal">{{translate("CANCEL")}}</button>
</div> </div>
{% endblock %}
</div> </div>
</form> </form>
<!-- Include validation rules --> <!-- Include validation rules -->

View File

@@ -3,6 +3,12 @@
{% endblock %} {% endblock %}
{% block body %} {% block body %}
{% if admin_fallback %}
<hr style="color: #990000" />
<h3 style="color: #990000">You are receiving this email as an organisation/site administrator!</h3>
<p style="color: #990000">This email has been sent to you in lieu of <b>{{organisation.name}}</b> as there are currently no approved administrators for the organisation.</p>
<hr style="color: #990000" />
{% endif %}
<p> <p>
Dear {{recipient.first_name}}, Dear {{recipient.first_name}},
</p> </p>

View File

@@ -0,0 +1,32 @@
{% extends "modals/modal.html.twig" %}
{% block modal_title %}{{translate("ORGANISATION.MEMBER.RESET_PASSWORD")}}{% endblock %}
{% block modal_body %}
<form class="js-form" method="POST" action="{{site.uri.public}}/api/organisations/o/{{organisation.slug}}/members/m/{{user.user_name}}/password-reset">
{% include "forms/csrf.html.twig" %}
<div class="js-form-alerts">
</div>
<div class="row">
<div class="col-sm-12">
{{translate("ORGANISATION.MEMBER.SEND_PASSWORD_LINK_CONFIRM", {
'user_name': user.user_name,
'email': user.email
})}}
</div>
</div>
<br>
<div class="row">
<div class="col-xs-8 col-sm-4">
<button type="submit" class="btn btn-block btn-lg btn-success">{{translate('CONFIRM')}}</button>
</div>
<div class="col-xs-4 col-sm-3 pull-right">
<button type="button" class="btn btn-block btn-lg btn-link" data-dismiss="modal">{{translate('CANCEL')}}</button>
</div>
</div>
</form>
<!-- Include validation rules -->
<script>
{% include "pages/partials/page.js.twig" %}
</script>
{% endblock %}

View File

@@ -0,0 +1,28 @@
{% if checkAccess('uri_organisations') %}
<li>
<a href="{{site.uri.public}}/organisations"><i class="fas fa-sitemap fa-fw"></i> <span>{{ translate("ORGANISATION", 2) }}</span></a>
</li>
{% endif %}
{% if current_user.organisations.count() > 0 %}
{% if organisationConfig.membership.single_membership == false %}
<li>
<a href="#" data-toggle="collapse" data-target="#submenu-organisations" aria-expanded="false"><i class="fa fa-fw fa-sitemap"></i> <span>{{ translate("ORGANISATION.SELF") }}</span> <i class="fa fa-fw pull-right fa-angle-down"></i></a>
<ul id="submenu-organisations" class="collapsable collapse" aria-expanded="false" style="height: 1px;">
{% for organisation in current_user.organisations %}
{% if organisation.flag_approved or isOrganisationRegistrant(organisation) %}
<li>
<a href="{{site.uri.public}}/organisations/o/{{organisation.slug}}"><i class="fas fa-angle-double-right"></i> <span>{{organisation.name}}</span></a>
</li>
{% endif %}
{% endfor %}
</ul>
</li>
{% else %}
{% set organisation = current_user.organisations.0 %}
{% if organisation.flag_approved or isOrganisationRegistrant(organisation) %}
<li>
<a href="{{site.uri.public}}/organisations/o/{{organisation.slug}}"><i class="fas fa-sitemap fa-fw"></i> <span>{{ translate("ORGANISATION.SELF") }}</span></a>
</li>
{% endif %}
{% endif %}
{% endif %}

View File

@@ -0,0 +1,3 @@
{% if checkAccess('uri_organisations') %}
<a href="{{site.uri.public}}/organisations" class="btn btn-default btn-flat btn-block">{{ translate("ORGANISATION", 2) }}</a>
{% endif %}

View File

@@ -1,37 +1,9 @@
{% extends "@admin/navigation/sidebar-menu.html.twig" %} {% extends "@admin/navigation/sidebar-menu.html.twig" %}
{% block organisations_nav %}
{% if checkAccess('uri_organisations') %}
<li>
<a href="{{site.uri.public}}/organisations"><i class="fas fa-sitemap fa-fw"></i> <span>{{ translate("ORGANISATION", 2) }}</span></a>
</li>
{% endif %}
{% if current_user.organisations.count() > 0 %}
{% if organisationConfig.membership.single_membership == false %}
<li>
<a href="#" data-toggle="collapse" data-target="#submenu-organisations" aria-expanded="false"><i class="fa fa-fw fa-sitemap"></i> <span>{{ translate("ORGANISATION.SELF") }}</span> <i class="fa fa-fw pull-right fa-angle-down"></i></a>
<ul id="submenu-organisations" class="collapsable collapse" aria-expanded="false" style="height: 1px;">
{% for organisation in current_user.organisations %}
{% if organisation.flag_approved or isOrganisationRegistrant(organisation) %}
<li>
<a href="{{site.uri.public}}/organisations/o/{{organisation.slug}}"><i class="fas fa-angle-double-right"></i> <span>{{organisation.name}}</span></a>
</li>
{% endif %}
{% endfor %}
</ul>
</li>
{% else %}
{% set organisation = current_user.organisations.0 %}
{% if organisation.flag_approved or isOrganisationRegistrant(organisation) %}
<li>
<a href="{{site.uri.public}}/organisations/o/{{organisation.slug}}"><i class="fas fa-sitemap fa-fw"></i> <span>{{ translate("ORGANISATION.SELF") }}</span></a>
</li>
{% endif %}
{% endif %}
{% endif %}
{% endblock %}
{% block navigation %} {% block navigation %}
{{ parent() }} {{ parent() }}
{{ block('organisations_nav') }}
{% block navigation_organisations %}
{% include "navigation/partials/sidebar-organisations.html.twig" %}
{% endblock %}
{% endblock %} {% endblock %}

View File

@@ -0,0 +1,11 @@
{% extends "@admin/navigation/user-card.html.twig" %}
{% block userCard_menu %}
<a href="{{site.uri.public}}" class="btn btn-default btn-flat btn-block">Homepage</a>
{% block navigation_organisations %}
{% include "navigation/partials/user-card-organisations.html.twig" %}
{% endblock %}
{{ parent() }}
{% endblock %}

View File

@@ -0,0 +1,15 @@
{% extends "@account/pages/account-settings.html.twig" %}
{% block body_matter %}
<div class="row">
<div class="col-lg-6">
{% block settings_profile_box %} {{ parent() }} {% endblock %}
{% block user_organisations_box %}
{% include "pages/partials/settings-organisations.html.twig" %}
{% endblock %}
</div>
<div class="col-lg-6">
{% block settings_account_box %} {{ parent() }} {% endblock %}
</div>
</div>
{% endblock %}

View File

@@ -1,10 +1,12 @@
{% extends "@blockier-templates/pages/dashboard.html.twig" %} {% extends "@uf-tweaks/pages/dashboard.html.twig" %}
{% block info_boxes_users %} {% block body_matter %}
{% if hasRole('site-admin') or hasRole('organisations-admin') %} {% set dashboard_is_empty = true %}
<!-- Info boxes -->
<div class="row"> <div class="row">
{% block info_box_users %}
{% if checkAccess('uri_users') %} {% if checkAccess('uri_users') %}
{% set dashboard_is_empty = false %}
<div class="col-lg-3 col-md-6 col-xs-12"> <div class="col-lg-3 col-md-6 col-xs-12">
<a href="{{site.uri.public}}/users"> <a href="{{site.uri.public}}/users">
<div class="info-box"> <div class="info-box">
@@ -20,10 +22,9 @@
</div> </div>
<!-- /.col --> <!-- /.col -->
{% endif %} {% endif %}
{% endblock %}
{% block info_box_roles %}
{% if checkAccess('uri_roles') %} {% if checkAccess('uri_roles') %}
{% set dashboard_is_empty = false %}
<div class="col-lg-3 col-md-6 col-xs-12"> <div class="col-lg-3 col-md-6 col-xs-12">
<a href="{{site.uri.public}}/roles"> <a href="{{site.uri.public}}/roles">
<div class="info-box"> <div class="info-box">
@@ -39,14 +40,13 @@
</div> </div>
<!-- /.col --> <!-- /.col -->
{% endif %} {% endif %}
{% endblock %}
{% block info_box_groups %}
{% if checkAccess('uri_groups') %} {% if checkAccess('uri_groups') %}
{% set dashboard_is_empty = false %}
<div class="col-lg-3 col-md-6 col-xs-12"> <div class="col-lg-3 col-md-6 col-xs-12">
<a href="{{site.uri.public}}/groups"> <a href="{{site.uri.public}}/groups">
<div class="info-box"> <div class="info-box">
<span class="info-box-icon bg-yellow"><i class="fas fa-users"></i></span> <span class="info-box-icon bg-green"><i class="fas fa-users"></i></span>
<div class="info-box-content"> <div class="info-box-content">
<span class="info-box-text">{{ translate("GROUP", 2) }}</span> <span class="info-box-text">{{ translate("GROUP", 2) }}</span>
<span class="info-box-number">{{counter.groups}}</span> <span class="info-box-number">{{counter.groups}}</span>
@@ -58,14 +58,13 @@
</div> </div>
<!-- /.col --> <!-- /.col -->
{% endif %} {% endif %}
{% endblock %}
{% block info_box_organisations %}
{% if checkAccess('uri_organisations') %} {% if checkAccess('uri_organisations') %}
{% set dashboard_is_empty = false %}
<div class="col-lg-3 col-md-6 col-xs-12"> <div class="col-lg-3 col-md-6 col-xs-12">
<a href="{{site.uri.public}}/organisations"> <a href="{{site.uri.public}}/organisations">
<div class="info-box"> <div class="info-box">
<span class="info-box-icon bg-green"><i class="fas fa-users"></i></span> <span class="info-box-icon bg-purple"><i class="fas fa-sitemap"></i></span>
<div class="info-box-content"> <div class="info-box-content">
<span class="info-box-text">{{ translate("ORGANISATION", 2) }}</span> <span class="info-box-text">{{ translate("ORGANISATION", 2) }}</span>
<span class="info-box-number">{{counter.organisations}}</span> <span class="info-box-number">{{counter.organisations}}</span>
@@ -77,18 +76,100 @@
</div> </div>
<!-- /.col --> <!-- /.col -->
{% endif %} {% endif %}
{% endblock %}
</div> </div>
<!-- /.row --> <!-- /.row -->
{% endif %}
{% endblock %}
<!-- Main panels -->
{% block latest_organisations %}
{% if (hasRole('site-admin') or hasRole('organisations-admin')) %}
<div class="row"> <div class="row">
<div class="col-sm-12"> {% if checkAccess('view_system_info') %}
{% set dashboard_is_empty = false %}
<div class="col-md-6 col-sm-12 col-xs-12">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">{{translate("SYSTEM_INFO")}}</h3>
</div>
<!-- /.box-header -->
<div class="box-body">
<dl class="dl-horizontal">
<dt>{{translate("SYSTEM_INFO.UF_VERSION")}}</dt>
<dd>{{info.version.UF}}</dd>
<dt>{{translate("SYSTEM_INFO.PHP_VERSION")}}</dt>
<dd>{{info.version.php}}</dd>
<dt>{{translate("SYSTEM_INFO.SERVER")}}</dt>
<dd>{{info.environment.SERVER_SOFTWARE}}</dd>
<dt>{{translate("SYSTEM_INFO.DB_VERSION")}}</dt>
<dd>{{info.version.database.type}} {{info.version.database.version}}</dd>
<dt>{{translate("SYSTEM_INFO.DB_NAME")}}</dt>
<dd>{{info.database.name}}</dd>
<dt>{{translate("SYSTEM_INFO.DIRECTORY")}}</dt>
<dd>{{info.path.project}}</dd>
<dt>{{translate("SYSTEM_INFO.URL")}}</dt>
<dd>{{site.uri.public}}</dd>
<dt>{{translate("SYSTEM_INFO.SPRINKLES")}}</dt>
<dd>
<ul class="list-unstyled">
{% for sprinkle in sprinkles %}
<li>
{{sprinkle}}
</li>
{% endfor %}
</ul>
</dd>
</dl>
</div>
<!-- /.box-body -->
<div class="box-footer text-center">
<a href="javascript:void(0)" class="js-clear-cache uppercase">{{ translate("CACHE.CLEAR") }}</a>
</div>
<!-- /.box-footer -->
</div>
<!--/.box -->
</div>
<!-- /.col -->
{% endif %}
{% if checkAccess('uri_users') %}
{% set dashboard_is_empty = false %}
<div class="col-md-6 col-sm-12 col-xs-12">
<!-- USERS LIST -->
<div class="box box-info">
<div class="box-header with-border">
<h3 class="box-title">{{translate("USER.LATEST")}}</h3>
</div>
<!-- /.box-header -->
<div class="box-body no-padding">
<ul class="users-list clearfix">
{% for user in users %}
<li>
<img src="{{ user.avatar }}" alt="User Image">
<a class="users-list-name" href="{{site.uri.public}}/users/u/{{user.user_name}}">{{user.first_name}} {{user.last_name}}</a>
<span class="users-list-date">{{ user.registered }}</span>
</li>
{% endfor %}
</ul>
<!-- /.users-list -->
</div>
<!-- /.box-body -->
<div class="box-footer text-center">
<a href="{{site.uri.public}}/users" class="uppercase">{{translate("USER.VIEW_ALL")}}</a>
</div>
<!-- /.box-footer -->
</div>
<!--/.box -->
</div>
<!-- /.col -->
{% endif %}
{% if hasRole('organisations-admin') %}
{% set dashboard_is_empty = false %}
<div class="col-md-6 col-sm-12 col-xs-12">
<!-- ORGANISTIONS LIST --> <!-- ORGANISTIONS LIST -->
<div class="box box-info"> <div class="box box-info">
<div class="box-header with-border"> <div class="box-header with-border">
@@ -97,10 +178,9 @@
<!-- /.box-header --> <!-- /.box-header -->
<div class="box-body no-padding clearfix"> <div class="box-body no-padding clearfix">
{% for organisation in organisations %} {% for organisation in organisations %}
<div class="col-sm-6 col-xs-12"> <div class="col-sm-6 col-xs-12">
<div class="box box-widget widget-user-2 widget-organisations"> <div class="box box-widget widget-user-2 widget-organisations">
<div class="widget-user-header bg-green"> <div class="widget-user-header bg-purple">
<h3 class="widget-user-username">{{organisation.name}}</h3> <h3 class="widget-user-username">{{organisation.name}}</h3>
<h5 class="widget-user-desc">{{organisation.description}}</h5> <h5 class="widget-user-desc">{{organisation.description}}</h5>
</div> </div>
@@ -114,7 +194,6 @@
</div> </div>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
<!-- /.organistions-list --> <!-- /.organistions-list -->
</div> </div>
@@ -127,49 +206,11 @@
<!--/.box --> <!--/.box -->
</div> </div>
<!-- /.col --> <!-- /.col -->
</div>
<!-- /.row -->
{% endif %}
{% endblock %}
{% set empty_dashboard = true %}
{% block main_panels %}
{% if
checkAccess('uri_users') or
checkAccess('view_group_field', {
'group': current_user.group,
'property': 'users'
}) or
hasRole('site-admin') or
hasRole('organisations-admin')
%}
{% set empty_dashboard = false %}
<div class="row">
{% block left_panels %}
<div class="col-md-6 col-sm-12 col-xs-12">
{{ block("latest_users") }}
{{ block("group_users_summary") }}
</div>
<!-- /.col -->
{% endblock %}
{% block right_panels %}
<div class="col-md-6 col-sm-12 col-xs-12">
{{ block("latest_organisations") }}
</div>
{% endblock %}
</div>
<!-- /.row -->
{% endif %} {% endif %}
{% if checkAccess('uri_activities') %} {% if checkAccess('uri_activities') %}
{% set empty_dashboard = false %} {% set dashboard_is_empty = false %}
<div class="row"> <div class="col-md-{% if (hasRole('auditer')) %}12{% else %}6{% endif %} col-sm-12 col-xs-12">
{% if checkAccess('uri_activities') %}
<div class="col-md-12 col-sm-12 col-xs-12">
<div id="widget-activities" class="box box-primary"> <div id="widget-activities" class="box box-primary">
<div class="box-header"> <div class="box-header">
<h3 class="box-title"><i class="fas fa-tasks fa-fw"></i> {{translate('ACTIVITY', 2)}}</h3> <h3 class="box-title"><i class="fas fa-tasks fa-fw"></i> {{translate('ACTIVITY', 2)}}</h3>
@@ -189,23 +230,111 @@
{% endif %} {% endif %}
</div> </div>
<!-- /.row --> <!-- /.row -->
{% endif %}
{% if
checkAccess('view_system_info') or {% if current_user.group %}
hasRole('site-admin') <!-- Group info boxes -->
%}
{% set empty_dashboard = false %}
<div class="row"> <div class="row">
<div class="col-md-6 col-sm-12 col-xs-12"> {% if checkAccess('uri_group', {
{{ block("system_info") }} 'group': current_user.group
}) %}
{% set dashboard_is_empty = false %}
<div class="col-sm-6 col-xs-12">
<div class="info-box">
<span class="info-box-icon bg-aqua"><i class="{{current_user.group.icon}}"></i></span>
<div class="info-box-content">
<h1>{{current_user.group.name}}</h1>
</div>
<!-- /.info-box-content -->
</div>
<!-- /.info-box -->
</div> </div>
<!-- /.col --> <!-- /.col -->
<div class="col-sm-6 col-xs-12">
<div class="info-box">
<span class="info-box-icon bg-aqua"><i class="fas fa-user fa-fw"></i></span>
<div class="info-box-content">
<span class="info-box-text">{{ translate("USER", 2) }}</span>
<span class="info-box-number">{{current_user.group.users.count}}</span>
</div>
<!-- /.info-box-content -->
</div>
<!-- /.info-box -->
</div>
<!-- /.col -->
{% endif %}
</div>
<!-- /.row -->
<!-- Main panels -->
<div class="row">
{% if checkAccess('view_group_field', {
'group': current_user.group,
'property': 'users'
}) %}
{% set dashboard_is_empty = false %}
<div class="col-md-offset-3 col-md-6 col-sm-12 col-xs-12">
<div id="widget-group-users" class="box box-primary">
<div class="box-header">
<h3 class="box-title"><i class="fas fa-fw fa-user"></i> {{translate('GROUP')}} {{translate('USER', 2)}}</h3>
{% include "tables/table-tool-menu.html.twig" %}
</div>
<div class="box-body">
{% include "tables/users.html.twig" with {
"table" : {
"id" : "table-group-users",
"columns" : [
(checkAccess('view_user_field', { "property" : 'activities' }) ? "last_activity" : "")
]
}
}
%}
</div>
<div class="box-footer">
<button type="button" class="btn btn-success js-user-create">
<i class="fas fa-plus-square"></i> {{translate("USER.CREATE")}}
</button>
</div>
</div>
</div>
{% endif %}
</div>
<!-- /.row -->
<!-- /User's group -->
{% endif %}
{% if dashboard_is_empty %}
<div class="row">
<div class="col-sm-4 col-sm-offset-4 col-xs-12">
<div class="box box-widget widget-user">
<!-- Add the bg color to the header using any of the bg-* classes -->
<div class="widget-user-header bg-black-active">
<h3 class="widget-user-username">
{{translate("WELCOME", {
'first_name': current_user.first_name
})}}
</h3>
</div>
<div class="widget-user-image">
<img class="img-circle" src="{{assets.url('assets://userfrosting/images/cupcake.png')}}" alt="User Avatar">
</div>
<div class="box-footer">
<h4>
{{translate("WELCOME_TO", {
'title': site.title
})}}
</h4>
<p>
{{translate("NO_FEATURES_YET")}}
</p>
</div>
</div>
<!-- /.widget-user -->
</div>
<!-- /.column -->
</div> </div>
<!-- /.row --> <!-- /.row -->
{% endif %} {% endif %}
{% if empty_dashboard == true %}
{{ block("user_welcome") }}
{% endif %}
{% endblock %} {% endblock %}

View File

@@ -1,4 +1,4 @@
{% extends "pages/abstract/dashboard.html.twig" %} {% extends "@admin/pages/abstract/dashboard.html.twig" %}
{% block stylesheets_page %} {% block stylesheets_page %}
<!-- Page-specific CSS asset bundle --> <!-- Page-specific CSS asset bundle -->
@@ -21,7 +21,8 @@
<div class="box-body"> <div class="box-body">
{% include "tables/deleted-organisations.html.twig" with { {% include "tables/deleted-organisations.html.twig" with {
"table" : { "table" : {
"id" : "table-organisations" "id" : "table-organisations",
"columns" : table.columns
} }
} }
%} %}

View File

@@ -1,4 +1,4 @@
{% extends "@blockier-templates/pages/abstract/dashboard.html.twig" %} {% extends "@admin/pages/abstract/dashboard.html.twig" %}
{% block stylesheets_page %} {% block stylesheets_page %}
<!-- Page-specific CSS asset bundle --> <!-- Page-specific CSS asset bundle -->

View File

@@ -1,4 +1,4 @@
{% extends "@blockier-templates/pages/abstract/dashboard.html.twig" %} {% extends "@admin/pages/abstract/dashboard.html.twig" %}
{% block stylesheets_page %} {% block stylesheets_page %}
<!-- Page-specific CSS asset bundle --> <!-- Page-specific CSS asset bundle -->
@@ -21,7 +21,8 @@
<div class="box-body"> <div class="box-body">
{% include "tables/organisations.html.twig" with { {% include "tables/organisations.html.twig" with {
"table" : { "table" : {
"id" : "table-organisations" "id" : "table-organisations",
"columns" : table.columns
} }
} }
%} %}
@@ -31,9 +32,6 @@
<button type="button" class="btn btn-success js-organisation-create"> <button type="button" class="btn btn-success js-organisation-create">
<i class="fas fa-plus-square"></i> {{translate("ORGANISATION.CREATE")}} <i class="fas fa-plus-square"></i> {{translate("ORGANISATION.CREATE")}}
</button> </button>
<button type="button" class="btn btn-danger js-organisation-viewDeleted">
<i class="fas fa-minus-square"></i> {{translate("VIEW_DELETED")}}
</button>
{% elseif checkAccess('register_organisation') %} {% elseif checkAccess('register_organisation') %}
{% if (organisationConfig.membership.single_membership == 0) or ((current_user.organisations.count == 0) and (current_user.pendingOrganisations.count == 0)) %} {% if (organisationConfig.membership.single_membership == 0) or ((current_user.organisations.count == 0) and (current_user.pendingOrganisations.count == 0)) %}
<button type="button" class="btn btn-success js-organisation-register"> <button type="button" class="btn btn-success js-organisation-register">
@@ -41,6 +39,11 @@
</button> </button>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if checkAccess('uri_deleted_organisations') %}
<button type="button" class="btn btn-danger js-organisation-viewDeleted">
<i class="fas fa-minus-square"></i> {{translate("VIEW_DELETED")}}
</button>
{% endif %}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,28 @@
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title"><i class="fas fa-sitemap"></i> {{translate('ORGANISATION', 2)}}</h3>
</div>
<div class="box-body">
<p class="box-profile-property">
{% for organisation in current_user.organisations %}
<div class="row">
<div class="col-xs-9">
<a href="{{site.uri.public}}/organisations/o/{{organisation.slug}}" class="label bg-primary {% if organisation.flag_approved != 1 %}organisation-pending{% endif %} {% if organisation.pivot.flag_admin %}organisation-admin{% endif %}" title="{{organisation.description}}" data-text="{{organisation.name}}" style="font-size: 100%;">{{organisation.name}}</a>
<small class="user-organisation-description">{{organisation.description}}</small>
</div>
</div>
<hr class="row-divider">
{% endfor %}
{% for organisation in current_user.pendingOrganisations %}
<div class="row">
<div class="col-xs-9">
<a href="{{site.uri.public}}/organisations/o/{{organisation.slug}}" class="label bg-primary {% if organisation.flag_approved != 1 %}organisation-pending{% endif %} {% if organisation.pivot.flag_admin %}organisation-admin{% endif %}" title="{{organisation.description}}" data-text="{{organisation.name}}" style="font-size: 100%;">{{organisation.name}}</a>
<small class="user-organisation-description">{{organisation.description}}</small>
</div>
</div>
<hr class="row-divider">
{% endfor %}
</p>
</div>
</div>

View File

@@ -1,6 +1,23 @@
{% extends "@blockier-templates/pages/user.html.twig" %} {% extends "@admin/pages/user.html.twig" %}
{% block body_matter %}
{% block group_box %}
{% endblock %}
<div class="row">
<div class="col-lg-4">
<div id="view-user">
{% block user_box %}
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">{{translate('USER.SUMMARY')}}</h3>
{% if 'tools' not in tools.hidden %}
<div class="box-tools pull-right">
<div class="btn-group">
<button type="button" class="btn btn-sm dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fas fa-cog"></i> <span class="caret"></span>
</button>
<ul class="dropdown-menu box-tool-menu">
{% block tools %} {% block tools %}
<li> <li>
<a href="#" class="js-user-edit" data-user_name="{{user.user_name}}"> <a href="#" class="js-user-edit" data-user_name="{{user.user_name}}">
@@ -58,10 +75,57 @@
</li> </li>
{% endif %} {% endif %}
{% endblock %} {% endblock %}
</ul>
</div>
</div>
{% endif %}
</div>
<div class="box-body box-profile">
<img class="profile-user-img img-responsive img-circle" src="{{user.avatar}}" alt="{{user.user_name}}">
<h3 class="profile-username text-center">{{user.full_name}}</h3>
<div class="text-center">
{% if user.flag_enabled == 0 %}
<i class="fas fa-fw fa-minus-circle fa-lg text-red" title="{{translate('DISABLED')}}"></i>
{% endif %}
{% if user.flag_verified == 0 %}
<i class="fas fa-fw fa-bolt fa-lg text-yellow" title="{{translate('UNACTIVATED')}}"></i>
{% endif %}
</div>
<h4 class="text-muted text-center">{{user.user_name}}{% if 'group' not in fields.hidden and user.group.name is not null %}{{user.group.name}}{% endif %}</h4>
{% block user_box %} {% if 'email' not in fields.hidden %}
{{ parent() }} <hr>
<strong><i class="fas fa-envelope margin-r-5"></i> {{translate("EMAIL")}}</strong>
<p class="text-muted box-profile-property js-copy-container">
<i class="fas fa-copy uf-copy-trigger js-copy-trigger"></i>
<span class="js-copy-target">{{user.email}}</span>
</p>
{% endif %}
{% if 'locale' not in fields.hidden %}
<hr>
<strong><i class="fas fa-language margin-r-5"></i> {{translate("LOCALE")}}</strong>
<p class="text-muted box-profile-property">
{{locales[user.locale]}}
</p>
{% endif %}
{% block user_profile %}{% endblock %}
{% if 'roles' not in fields.hidden %}
<hr>
<strong><i class="fas fa-id-card margin-r-5"></i> {{translate("ROLE", 2)}}</strong>
<p class="box-profile-property">
{% for role in user.roles %}
<span class="label label-primary" title="{{role.description}}">{{role.name}}</span>
{% endfor %}
</p>
{% endif %}
</div>
</div>
{% endblock %}
{% block organisations_box %}
<div class="box box-primary"> <div class="box box-primary">
<div class="box-header with-border"> <div class="box-header with-border">
<h3 class="box-title"><i class="fas fa-sitemap"></i> {{translate('USER.ORGANISATIONS')}}</h3> <h3 class="box-title"><i class="fas fa-sitemap"></i> {{translate('USER.ORGANISATIONS')}}</h3>
@@ -75,7 +139,7 @@
<br> <br>
<small class="user-organisation-description">{{organisation.description}}</small> <small class="user-organisation-description">{{organisation.description}}</small>
</div> </div>
{% if hasRole('site-admin') or hasRole('organisations-admin') -%} {% if checkAccess('update_user_field', { 'user' : user, 'fields' : ['organisations'] }) or hasRole('organisations-admin') -%}
<div class="col-xs-3" style="margin-top: -5px;"> <div class="col-xs-3" style="margin-top: -5px;">
<button type="button" data-slug="{{organisation.slug}}" data-user_name="{{user.user_name}}" class="btn btn-danger js-member-remove margin-r-5" style="min-width: 70px">{{translate("REMOVE")}}</button> <button type="button" data-slug="{{organisation.slug}}" data-user_name="{{user.user_name}}" class="btn btn-danger js-member-remove margin-r-5" style="min-width: 70px">{{translate("REMOVE")}}</button>
</div> </div>
@@ -90,7 +154,7 @@
<br> <br>
<small class="user-organisation-description">{{organisation.description}}</small> <small class="user-organisation-description">{{organisation.description}}</small>
</div> </div>
{% if hasRole('site-admin') or hasRole('organisations-admin') -%} {% if checkAccess('update_user_field', { 'user' : user, 'fields' : ['organisations'] }) or hasRole('organisations-admin') -%}
<div class="col-xs-3" style="margin-top: -5px;"> <div class="col-xs-3" style="margin-top: -5px;">
<button type="button" data-slug="{{organisation.slug}}" data-user_name="{{user.user_name}}" class="btn btn-danger js-member-remove margin-r-5" style="min-width: 70px">{{translate("REMOVE")}}</button> <button type="button" data-slug="{{organisation.slug}}" data-user_name="{{user.user_name}}" class="btn btn-danger js-member-remove margin-r-5" style="min-width: 70px">{{translate("REMOVE")}}</button>
</div> </div>
@@ -102,3 +166,50 @@
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
</div>
</div>
{% if checkAccess('view_user_field', { 'user': user, 'property': 'activities' }) %}
{% if 'activities' not in widgets.hidden %}
<div class="col-lg-8">
{% block activity_box %}
<div id="widget-user-activities" class="box box-primary">
<div class="box-header">
<h3 class="box-title"><i class="fas fa-fw fa-tasks"></i> {{translate('ACTIVITY', 2)}}</h3>
{% include "tables/table-tool-menu.html.twig" %}
</div>
<div class="box-body">
{% include "tables/activities.html.twig" with {
"table" : {
"id" : "table-user-activities"
}
}
%}
</div>
</div>
{% endblock %}
</div>
{% endif %}
{% endif %}
</div>
{% if 'permissions' not in widgets.hidden %}
<div class="row">
<div class="col-md-12">
<div id="widget-permissions" class="box box-primary">
<div class="box-header">
<h3 class="box-title pull-left"><i class="fas fa-key fa-fw"></i> {{translate('PERMISSION', 2)}}</h3>
{% include "tables/table-tool-menu.html.twig" %}
</div>
<div class="box-body">
{% include "tables/permissions.html.twig" with {
"table" : {
"id" : "table-permissions",
"columns" : ["via_roles"]
}
}
%}
</div>
</div>
</div>
</div>
{% endif %}
{% endblock %}

View File

@@ -1,6 +1,5 @@
{% block table_cell_template_actions %}
<script id="{{table.id}}-column-actions" type="text/x-handlebars-template"> <script id="{{table.id}}-column-actions" type="text/x-handlebars-template">
{%- verbatim %} {% verbatim %}
<td> <td>
<div class="btn-group"> <div class="btn-group">
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown"> <button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown">
@@ -21,6 +20,5 @@
</ul> </ul>
</div> </div>
</td> </td>
{% endverbatim -%} {% endverbatim %}
</script> </script>
{% endblock %}

View File

@@ -1,11 +1,9 @@
{% block table_cell_template_info %}
<script id="{{table.id}}-column-info" type="text/x-handlebars-template"> <script id="{{table.id}}-column-info" type="text/x-handlebars-template">
{%- verbatim %} {% verbatim %}
<td data-text="{{row.name}}"> <td data-text="{{row.name}}">
<strong> <strong>
<a href="{{site.uri.public}}/organisations/o/{{row.slug}}">{{row.name}}</a> <a href="{{site.uri.public}}/organisations/o/{{row.slug}}">{{row.name}}</a>
</strong> </strong>
</td> </td>
{% endverbatim -%} {% endverbatim %}
</script> </script>
{% endblock %}

View File

@@ -0,0 +1,54 @@
<script id="{{table.id}}-column-actions" type="text/x-handlebars-template">
{% if checkAccess('update_organisation_field', { 'organisation': organisation, 'fields': [ 'members' ] }) or isOrganisationAdmin(organisation) %}
{% verbatim %}
<td class="uf-table-fit-width">
<div class="btn-group">
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown">{% endverbatim %}{{translate("ACTIONS")}}{% verbatim %}<span class="caret"></span></button>
<ul class="dropdown-menu dropdown-menu-right-responsive" role="menu">
{{#ifx row.membership_approved '!=' 1 }}
<li>
<a href="#" data-slug="{% endverbatim %}{{organisation.slug}}{% verbatim %}" data-user_name="{{row.user_name}}" class="js-member-accept">
<i class="fas fa-thumbs-up"></i> {% endverbatim %}{{translate("ORGANISATION.JOIN_REQUEST.ACCEPT")}}{% verbatim %}
</a>
</li>
<li>
<a href="#" data-slug="{% endverbatim %}{{organisation.slug}}{% verbatim %}" data-user_name="{{row.user_name}}" class="js-member-reject">
<i class="fas fa-thumbs-down"></i> {% endverbatim %}{{translate("ORGANISATION.JOIN_REQUEST.REJECT")}}{% verbatim %}
</a>
</li>
{{ else }}
{{#ifx row.organisation_admin '==' 1 }}
<li>
<a href="#" data-slug="{% endverbatim %}{{organisation.slug}}{% verbatim %}" data-user_name="{{row.user_name}}" class="js-member-demote">
<i class="fas fa-angle-double-down"></i> {% endverbatim %}{{translate("MEMBER.DEMOTE")}}{% verbatim %}
</a>
</li>
{{ else }}
<li>
<a href="#" data-slug="{% endverbatim %}{{organisation.slug}}{% verbatim %}" data-user_name="{{row.user_name}}" class="js-member-promote">
<i class="fas fa-angle-double-up"></i> {% endverbatim %}{{translate("MEMBER.PROMOTE")}}{% verbatim %}
</a>
</li>
{{/ifx}}
<li>
<a href="#" data-slug="{% endverbatim %}{{organisation.slug}}{% verbatim %}" data-user_name="{{row.user_name}}" class="js-user-edit">
<i class="fas fa-edit"></i> {% endverbatim %}{{translate("MEMBER.EDIT")}}{% verbatim %}
</a>
</li>
<li>
<a href="#" data-slug="{% endverbatim %}{{organisation.slug}}{% verbatim %}" data-user_name="{{row.user_name}}" class="js-member-password">
<i class="fas fa-key"></i> {% endverbatim %}{{translate("MEMBER.RESET_PASSWORD")}}{% verbatim %}
</a>
</li>
<li>
<a href="#" data-slug="{% endverbatim %}{{organisation.slug}}{% verbatim %}" data-user_name="{{row.user_name}}" class="js-member-remove">
<i class="fas fa-door-open"></i> {% endverbatim %}{{translate("MEMBER.REMOVE")}}{% verbatim %}
</a>
</li>
{{/ifx}}
</ul>
</div>
</td>
{% endverbatim %}
{% endif %}
</script>

View File

@@ -0,0 +1,19 @@
<script id="{{table.id}}-column-info" type="text/x-handlebars-template">
{% verbatim %}
<td data-text="{{row.last_name}}">
<strong>
{% endverbatim -%}{% if isOrganisationAdmin(organisation) %}{%- verbatim %}
<a href="{{site.uri.public}}/users/u/{{row.user_name}}">{{row.first_name}} {{row.last_name}} ({{row.user_name}})</a>
{% endverbatim -%}{% elseif checkAccess('uri_user') %}{%- verbatim %}
<a href="{{site.uri.public}}/users/u/{{row.user_name}}">{{row.first_name}} {{row.last_name}} ({{row.user_name}})</a>
{% endverbatim -%}{% else %}{%- verbatim %}
{{row.first_name}} {{row.last_name}} ({{row.user_name}})
{% endverbatim -%}{% endif %}{%- verbatim %}
</strong>
<div class="js-copy-container">
<span class="js-copy-target">{{row.email}}</span>
<button class="btn btn-xs uf-copy-trigger js-copy-trigger"><i class="fas fa-copy"></i></button>
</div>
</td>
{% endverbatim %}
</script>

View File

@@ -1,6 +1,5 @@
{% block table_cell_template_status %}
<script id="{{table.id}}-column-status" type="text/x-handlebars-template"> <script id="{{table.id}}-column-status" type="text/x-handlebars-template">
{%- verbatim %} {% verbatim %}
<td <td
{{#ifx row.membership_approved '==' 0 }} {{#ifx row.membership_approved '==' 0 }}
data-text="pending" data-text="pending"
@@ -15,6 +14,5 @@
{{#ifx row.organisation_admin '==' 1 }}<span class="text-secondary">({% endverbatim %}{{translate("ADMIN")}}{% verbatim %})</span>{{/ifx}} {{#ifx row.organisation_admin '==' 1 }}<span class="text-secondary">({% endverbatim %}{{translate("ADMIN")}}{% verbatim %})</span>{{/ifx}}
{{/ifx }} {{/ifx }}
</td> </td>
{% endverbatim -%} {% endverbatim %}
</script> </script>
{% endblock %}

View File

@@ -0,0 +1,134 @@
<script id="{{table.id}}-column-actions" type="text/x-handlebars-template">
{% if 'actions' in table.columns %}
<td>
{% if not organisationConfig.combine_action_buttons %}
{% if not (checkAccess('delete_organisation') or checkAccess('approve_organisation') or checkAccess('merge_organisations')) %}
{% verbatim %}{{#ifx row.is_admin '==' 1 }}{% endverbatim %}
{% endif %}
{% endif %}
<div class="btn-group">
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown">
{{translate("ACTIONS")}}
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right" role="menu">
{% block organisation_membership_actions %}
{% if organisationConfig.combine_action_buttons %}
{% verbatim %}{{#ifx row.is_pending '==' 1 }}{% endverbatim %}
<li>
<a href="#" data-slug="{% verbatim %}{{row.slug}}{% endverbatim %}" class="js-organisation-cancelJoin">
<i class="fas fa-window-close"></i>
{{translate("JOIN.CANCEL")}}
</a>
</li>
{% verbatim %}{{ else }}{% endverbatim %}
{% verbatim %}{{#ifx row.is_member '==' 1 }}{% endverbatim %}
<li>
<a href="#" data-slug="{% verbatim %}{{row.slug}}{% endverbatim %}" class="js-organisation-leave">
<i class="fas fa-sign-out-alt"></i>
{{translate("LEAVE")}}
</a>
</li>
{% verbatim %}{{ else }}{% endverbatim %}
{% if (organisationConfig.membership.single_membership == 0) or ((current_user.organisations.count == 0) and (current_user.pendingOrganisations.count == 0)) %}
<li>
<a href="#" data-slug="{% verbatim %}{{row.slug}}{% endverbatim %}" class="js-organisation-join">
<i class="fas fa-sign-in-alt"></i>
{{translate("JOIN")}}
</a>
</li>
{% endif %}
{% verbatim %}{{/ifx}}{% endverbatim %}
{% verbatim %}{{/ifx}}{% endverbatim %}
{% if checkAccess('merge_organisations') or checkAccess('approve_organisation') or checkAccess('delete_organisation') %}
<li class="divider"></li>
{% else %}
{% if organisationConfig.combine_action_buttons %}
{% if not (checkAccess('delete_organisation') or checkAccess('approve_organisation') or checkAccess('merge_organisations')) %}
{% verbatim %}{{#ifx row.is_admin '==' 1 }}<li class="divider"></li>{{/ifx}}{% endverbatim %}
{% endif %}
{% endif %}
{% endif %}
{% endif %}
{% endblock %}
{% block organisation_administration_actions %}
{% if organisationConfig.combine_action_buttons %}
{% if not (checkAccess('delete_organisation') or checkAccess('approve_organisation') or checkAccess('merge_organisations')) %}
{% verbatim %}{{#ifx row.is_admin '==' 1 }}{% endverbatim %}
{% endif %}
{% endif %}
<li>
<a href="#" data-slug="{% verbatim %}{{row.slug}}{% endverbatim %}" class="js-organisation-edit">
<i class="fas fa-edit"></i>
{{translate("ORGANISATION.EDIT")}}
</a>
</li>
{% if organisationConfig.combine_action_buttons %}
{% if not (checkAccess('delete_organisation') or checkAccess('approve_organisation') or checkAccess('merge_organisations')) %}
{% verbatim %}{{/ifx }}{% endverbatim %}
{% endif %}
{% endif %}
{% if checkAccess('approve_organisation') %}
{% verbatim %}{{#ifx row.flag_approved '==' 0 }}{% endverbatim %}
{% verbatim %}{{#ifx row.registrant_id '==' {% endverbatim %}{{current_user.id}}{% verbatim %} }}{% endverbatim %}
<li>
<a href="#" data-slug="{% verbatim %}{{row.slug}}{% endverbatim %}" class="js-organisation-cancelRegistration">
<i class="fas fa-trash-alt"></i>
{{translate("ORGANISATION.REGISTRATION.CANCEL")}}
</a>
</li>
{% verbatim %}{{/ifx}}{% endverbatim %}
<li>
<a href="#" data-slug="{% verbatim %}{{row.slug}}{% endverbatim %}" class="js-organisation-approveRegistration">
<i class="fas fa-thumbs-up"></i> {{translate("ORGANISATION.REGISTRATION.APPROVE")}}
</a>
</li>
<li>
<a href="#" data-slug="{% verbatim %}{{row.slug}}{% endverbatim %}" class="js-organisation-denyRegistration">
<i class="fas fa-thumbs-down"></i>
{{translate("ORGANISATION.REGISTRATION.DENY")}}
</a>
</li>
{% verbatim %}{{/ifx}}{% endverbatim %}
{% endif %}
{% if checkAccess('merge_organisations') %}
<li>
<a href="#" data-slug="{% verbatim %}{{row.slug}}{% endverbatim %}" class="js-organisation-merge">
<i class="fas fa-object-group"></i>
{{translate("ORGANISATION.MERGE")}}
</a>
</li>
{% endif %}
{% if checkAccess('delete_organisation') %}
{% verbatim %}{{#ifx row.flag_approved '==' 1 }}{% endverbatim %}
<li>
<a href="#" data-slug="{% verbatim %}{{row.slug}}{% endverbatim %}" class="js-organisation-delete">
<i class="fas fa-trash-alt"></i>
{{translate("ORGANISATION.DELETE")}}
</a>
</li>
{% verbatim %}{{/ifx }}{% endverbatim %}
{% endif %}
{% endblock %}
</ul>
</div>
{% if not organisationConfig.combine_action_buttons %}
{% if not (checkAccess('delete_organisation') or checkAccess('approve_organisation') or checkAccess('merge_organisations')) %}
{% verbatim %}{{/ifx}}{% endverbatim %}
{% endif %}
{% endif %}
</td>
{% endif %}
</script>

View File

@@ -0,0 +1,9 @@
<script id="{{table.id}}-column-adminCount" type="text/x-handlebars-template">
{% if checkAccess('view_organisation_field', { 'property' : 'members' }) %}
{% verbatim %}
<td>
{{row.admin_count}}
</td>
{% endverbatim %}
{% endif %}
</script>

View File

@@ -0,0 +1 @@
{% include "@uf-tweaks/tables/columns/abstract/description.html.twig" %}

View File

@@ -0,0 +1,17 @@
<script id="{{table.id}}-column-info" type="text/x-handlebars-template">
{% verbatim %}
<td data-text="{{row.name}}">
<strong>
{{#ifx row.is_member '==' 1 }}
<a href="{{site.uri.public}}/organisations/o/{{row.slug}}">{{row.name}}</a>
{{ else }}
{% endverbatim %}{% if checkAccess('uri_organisation') %}{% verbatim %}
<a href="{{site.uri.public}}/organisations/o/{{row.slug}}">{{row.name}}</a>
{% endverbatim %}{% else %}{% verbatim %}
{{row.name}}
{% endverbatim %}{% endif %}{% verbatim %}
{{/ifx }}
</strong>
</td>
{% endverbatim %}
</script>

View File

@@ -0,0 +1,29 @@
<script id="{{table.id}}-column-join" type="text/x-handlebars-template">
{% if not organisationConfig.combine_action_buttons %}
{% if 'join' in table.columns %}
{% verbatim %}
<td>
<div class="btn-group">
{{#ifx row.is_pending '==' 1 }}
<button type="button" data-slug="{{row.slug}}" class="btn btn-danger js-organisation-cancelJoin" style="min-width: 70px">
{% endverbatim %}{{translate("CANCEL")}}{% verbatim %}
</button>
{{ else }}
{{#ifx row.is_member '==' 1 }}
<button type="button" data-slug="{{row.slug}}" class="btn btn-danger js-organisation-leave" style="min-width: 70px">
{% endverbatim %}{{translate("LEAVE")}}{% verbatim %}
</button>
{{ else }}
{% endverbatim %}{% if (organisationConfig.membership.single_membership == 0) or ((current_user.organisations.count == 0) and (current_user.pendingOrganisations.count == 0)) %}{% verbatim %}
<button type="button" data-slug="{{row.slug}}" class="btn btn-success js-organisation-join" style="min-width: 70px">
{% endverbatim %}{{translate("JOIN")}}{% verbatim %}
</button>
{% endverbatim %}{% endif %}{% verbatim %}
{{/ifx}}
{{/ifx}}
</div>
</td>
{% endverbatim %}
{% endif %}
{% endif %}
</script>

View File

@@ -0,0 +1,9 @@
<script id="{{table.id}}-column-memberCount" type="text/x-handlebars-template">
{% if checkAccess('view_organisation_field', { 'property' : 'members' }) %}
{% verbatim %}
<td>
{{row.member_count}}
</td>
{% endverbatim %}
{% endif %}
</script>

View File

@@ -0,0 +1,25 @@
<script id="{{table.id}}-column-status" type="text/x-handlebars-template">
{% if 'status' in table.columns %}
{% verbatim %}
<td
{{#ifx row.flag_approved '==' 1 }}
data-text="approved"
{{ else }}
data-text="pending"
{{/ifx }}
>
{% endverbatim %}{# {{# ifx row.flag_admin '==' 1 }} #}{% verbatim %}
{{#ifx row.flag_approved '==' 1 }}
<span>
{% endverbatim %}{{translate("APPROVED")}}{% verbatim %}
</span>
{{ else }}
<span class="text-yellow">
{% endverbatim %}{{translate("PENDING")}}{% verbatim %}
</span>
{{/ifx }}
{% endverbatim %}{# {{/ifx }} #}{% verbatim %}
</td>
{% endverbatim %}
{% endif %}
</script>

View File

@@ -1,6 +1,5 @@
{% block table_cell_template_actions %}
<script id="{{table.id}}-column-actions" type="text/x-handlebars-template"> <script id="{{table.id}}-column-actions" type="text/x-handlebars-template">
{%- verbatim %} {% verbatim %}
<td class="uf-table-fit-width"> <td class="uf-table-fit-width">
<div class="btn-group"> <div class="btn-group">
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown">{% endverbatim %}{{translate("ACTIONS")}}{% verbatim %}<span class="caret"></span></button> <button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown">{% endverbatim %}{{translate("ACTIONS")}}{% verbatim %}<span class="caret"></span></button>
@@ -51,6 +50,5 @@
</ul> </ul>
</div> </div>
</td> </td>
{% endverbatim -%} {% endverbatim %}
</script> </script>
{% endblock %}

View File

@@ -1,6 +1,5 @@
{% block table_cell_template_organisations %}
<script id="{{table.id}}-column-organisations" type="text/x-handlebars-template"> <script id="{{table.id}}-column-organisations" type="text/x-handlebars-template">
{%- verbatim %} {% verbatim %}
<td style="line-height: 2em;"> <td style="line-height: 2em;">
{{#if row.organisations.length }} {{#if row.organisations.length }}
{{#each row.organisations }} {{#each row.organisations }}
@@ -13,6 +12,5 @@
{{/each}} {{/each}}
{{/if }} {{/if }}
</td> </td>
{% endverbatim -%} {% endverbatim %}
</script> </script>
{% endblock %}

View File

@@ -1,8 +1,5 @@
{% extends "tables/organisations.html.twig" %} {% extends "tables/organisations.html.twig" %}
{% use 'tables/partials/deleted-organisations/column-info.html.twig' %}
{% use 'tables/partials/deleted-organisations/column-actions.html.twig' %}
{% block table %} {% block table %}
<table id="{{table.id}}" class="tablesorter table table-bordered table-hover table-striped" data-sortlist="{{table.sortlist}}"> <table id="{{table.id}}" class="tablesorter table table-bordered table-hover table-striped" data-sortlist="{{table.sortlist}}">
<thead> <thead>
@@ -20,4 +17,12 @@
</table> </table>
{% endblock %} {% endblock %}
{% block table_cell_template_join %}{% endblock %} {% block table_organisations_column_info %}
{% include 'tables/columns/deleted_organisations-info.html.twig' %}
{% endblock %}
{% block table_organisations_column_actions %}
{% include 'tables/columns/deleted_organisations-actions.html.twig' %}
{% endblock %}
{% block table_organisations_column_join %}{% endblock %}

View File

@@ -1,8 +1,5 @@
{% extends "tables/users.html.twig" %} {% extends "tables/users.html.twig" %}
{% use 'tables/partials/organisation-members/column-status.html.twig' %}
{% use 'tables/partials/organisation-members/column-actions.html.twig' %}
{% block table %} {% block table %}
<table id="{{table.id}}" class="tablesorter table table-bordered table-hover table-striped" data-sortlist="{{table.sortlist}}"> <table id="{{table.id}}" class="tablesorter table table-bordered table-hover table-striped" data-sortlist="{{table.sortlist}}">
<thead> <thead>
@@ -10,7 +7,7 @@
<th class="sorter-metatext" data-column-name="name" data-column-template="#{{table.id}}-column-info" data-priority="1">{{translate('USER')}} <i class="fas fa-sort"></i></th> <th class="sorter-metatext" data-column-name="name" data-column-template="#{{table.id}}-column-info" data-priority="1">{{translate('USER')}} <i class="fas fa-sort"></i></th>
<th class="filter-metatext" data-column-name="organisations" data-sorter="false" data-column-template="#{{table.id}}-column-organisations" data-priority="2">{{translate("ORGANISATION", 2)}}</th> <th class="filter-metatext" data-column-name="organisations" data-sorter="false" data-column-template="#{{table.id}}-column-organisations" data-priority="2">{{translate("ORGANISATION", 2)}}</th>
{% if 'last_activity' in table.columns %} {% if 'last_activity' in table.columns %}
<th class="sorter-metanum" data-column-name="last_activity" data-column-template="#{{table.id}}-column-last-activity" data-priority="3">{{translate("ACTIVITY.LAST")}} <i class="fas fa-sort"></i></th> <th class="sorter-metanum" data-column-name="last_activity" data-column-template="#{{table.id}}-column-lastActivity" data-priority="3">{{translate("ACTIVITY.LAST")}} <i class="fas fa-sort"></i></th>
{% endif %} {% endif %}
<th class="filter-select filter-metatext" data-column-name="status" data-column-template="#{{table.id}}-column-status" data-priority="2">{{translate("STATUS")}} <i class="fas fa-sort"></i></th> <th class="filter-select filter-metatext" data-column-name="status" data-column-template="#{{table.id}}-column-status" data-priority="2">{{translate("STATUS")}} <i class="fas fa-sort"></i></th>
{% if checkAccess('update_organisation_field', { 'organisation': organisation, 'fields': [ 'members' ] }) or isOrganisationAdmin(organisation) %} {% if checkAccess('update_organisation_field', { 'organisation': organisation, 'fields': [ 'members' ] }) or isOrganisationAdmin(organisation) %}
@@ -22,3 +19,15 @@
</tbody> </tbody>
</table> </table>
{% endblock %} {% endblock %}
{% block table_users_column_info %}
{% include 'tables/columns/organisation_members-info.html.twig' %}
{% endblock %}
{% block table_users_column_status %}
{% include 'tables/columns/organisation_members-status.html.twig' %}
{% endblock %}
{% block table_users_column_actions %}
{% include 'tables/columns/organisation_members-actions.html.twig' %}
{% endblock %}

View File

@@ -7,36 +7,28 @@
{% extends "tables/table-paginated.html.twig" %} {% extends "tables/table-paginated.html.twig" %}
{% use 'tables/partials/organisations/column-info.html.twig' %}
{% use 'tables/partials/organisations/column-description.html.twig' %}
{% use 'tables/partials/organisations/column-status.html.twig' %}
{% use 'tables/partials/organisations/column-member_count.html.twig' %}
{% use 'tables/partials/organisations/column-admin_count.html.twig' %}
{% use 'tables/partials/organisations/column-join.html.twig' %}
{% use 'tables/partials/organisations/column-actions.html.twig' %}
{% block table %} {% block table %}
<table id="{{table.id}}" class="tablesorter table table-bordered table-hover table-striped" data-sortlist="{{table.sortlist}}"> <table id="{{table.id}}" class="tablesorter table table-bordered table-hover table-striped" data-sortlist="{{table.sortlist}}">
<thead> <thead>
<tr> <tr>
<th class="sorter-metatext filter-metatext" data-column-name="name" data-column-template="#{{table.id}}-column-info" data-priority="1">{{translate('ORGANISATION')}} <i class="fas fa-sort"></i></th> <th class="sorter-metatext filter-metatext" data-column-name="name" data-column-template="#{{table.id}}-column-info" data-priority="1">{{translate('ORGANISATION')}} <i class="fas fa-sort"></i></th>
{% if 'description' not in table.hidden_columns %} {% if 'description' in table.columns %}
<th class="sorter-metatext filter-metatext" data-column-name="description" data-column-template="#{{table.id}}-column-description" data-priority="2">{{translate("DESCRIPTION")}} <i class="fas fa-sort"></i></th> <th class="sorter-metatext filter-metatext" data-column-name="description" data-column-template="#{{table.id}}-column-description" data-priority="2">{{translate("DESCRIPTION")}} <i class="fas fa-sort"></i></th>
{% endif %} {% endif %}
{% if hasRole('site-admin') or hasRole('organisations-admin') or (current_user.adminForOrganisations.count > 0) -%} {% if 'status' in table.columns %}
<th class="filter-select filter-metatext" data-column-name="status" data-column-template="#{{table.id}}-column-status" data-priority="2">{{translate("STATUS")}} <i class="fas fa-sort"></i></th> <th class="filter-select filter-metatext" data-column-name="status" data-column-template="#{{table.id}}-column-status" data-priority="2">{{translate("STATUS")}} <i class="fas fa-sort"></i></th>
{% endif -%} {% endif %}
{% if checkAccess('view_organisation_members') -%} {% if checkAccess('view_organisation_field', { 'property' : 'members' }) %}
<th class="sorter-metanum" data-column-name="member_count" data-column-template="#{{table.id}}-column-member_count" data-priority="2" style="min-width: 180px">{{translate("ORGANISATION.MEMBER_COUNT")}} <i class="fas fa-sort"></i></th> <th class="sorter-metanum" data-column-name="member_count" data-column-template="#{{table.id}}-column-memberCount" data-priority="2" style="min-width: 180px">{{translate("ORGANISATION.MEMBER_COUNT")}} <i class="fas fa-sort"></i></th>
<th class="sorter-metanum" data-column-name="admin_count" data-column-template="#{{table.id}}-column-admin_count" data-priority="2">{{translate("ORGANISATION.ADMIN_COUNT")}} <i class="fas fa-sort"></i></th> <th class="sorter-metanum" data-column-name="admin_count" data-column-template="#{{table.id}}-column-adminCount" data-priority="2">{{translate("ORGANISATION.ADMIN_COUNT")}} <i class="fas fa-sort"></i></th>
{% endif -%} {% endif %}
{% if not organisationConfig.combine_action_buttons %}
{% if 'join' in table.columns %} {% if 'join' in table.columns %}
<th data-column-template="#{{table.id}}-column-join" data-column-name="join" data-sorter="false" data-filter="false" data-priority="3">{{translate("JOIN")}}/{{translate("LEAVE")}}</th> <th data-column-template="#{{table.id}}-column-join" data-column-name="join" data-sorter="false" data-filter="false" data-priority="3">{{translate("JOIN")}}/{{translate("LEAVE")}}</th>
{% endif %} {% endif %}
{% endif %}
{% if 'actions' in table.columns %} {% if 'actions' in table.columns %}
{% if hasRole('site-admin') or hasRole('organisations-admin') or (current_user.adminForOrganisations.count > 0) -%}
<th data-column-template="#{{table.id}}-column-actions" data-sorter="false" data-filter="false" data-priority="1">{{translate("ACTIONS")}}</th> <th data-column-template="#{{table.id}}-column-actions" data-sorter="false" data-filter="false" data-priority="1">{{translate("ACTIONS")}}</th>
{% endif -%}
{% endif %} {% endif %}
</tr> </tr>
</thead> </thead>
@@ -46,13 +38,27 @@
{% endblock %} {% endblock %}
{% block table_cell_templates %} {% block table_cell_templates %}
{{ block('table_cell_template_info') }} {% block table_organisations_column_info %}
{{ block('table_cell_template_description') }} {% include "tables/columns/organisations-info.html.twig" %}
{{ block('table_cell_template_status') }} {% endblock %}
{{ block('table_cell_template_member_count') }} {% block table_organisations_column_description %}
{{ block('table_cell_template_admin_count') }} {% include "tables/columns/organisations-description.html.twig" %}
{{ block('table_cell_template_join') }} {% endblock %}
{{ block('table_cell_template_actions') }} {% block table_organisations_column_status %}
{% include "tables/columns/organisations-status.html.twig" %}
{% endblock %}
{% block table_organisations_column_member_count %}
{% include "tables/columns/organisations-member_count.html.twig" %}
{% endblock %}
{% block table_organisations_column_admin_count %}
{% include "tables/columns/organisations-admin_count.html.twig" %}
{% endblock %}
{% block table_organisations_column_join %}
{% include "tables/columns/organisations-join.html.twig" %}
{% endblock %}
{% block table_organisations_column_actions %}
{% include "tables/columns/organisations-actions.html.twig" %}
{% endblock %}
{% endblock %} {% endblock %}

View File

@@ -1,56 +0,0 @@
{% block table_cell_template_actions %}
{% if checkAccess('update_organisation_field', { 'organisation': organisation, 'fields': [ 'members' ] }) or isOrganisationAdmin(organisation) %}
<script id="{{table.id}}-column-actions" type="text/x-handlebars-template">
{%- verbatim %}
<td class="uf-table-fit-width">
<div class="btn-group">
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown">{% endverbatim %}{{translate("ACTIONS")}}{% verbatim %}<span class="caret"></span></button>
<ul class="dropdown-menu dropdown-menu-right-responsive" role="menu">
{{#ifx row.membership_approved '!=' 1 }}
<li>
<a href="#" data-slug="{% endverbatim %}{{organisation.slug}}{% verbatim %}" data-user_name="{{row.user_name}}" class="js-member-accept">
<i class="fas fa-thumbs-up"></i> {% endverbatim %}{{translate("ORGANISATION.JOIN_REQUEST.ACCEPT")}}{% verbatim %}
</a>
</li>
<li>
<a href="#" data-slug="{% endverbatim %}{{organisation.slug}}{% verbatim %}" data-user_name="{{row.user_name}}" class="js-member-reject">
<i class="fas fa-thumbs-down"></i> {% endverbatim %}{{translate("ORGANISATION.JOIN_REQUEST.REJECT")}}{% verbatim %}
</a>
</li>
{{ else }}
{{#ifx row.organisation_admin '==' 1 }}
<li>
<a href="#" data-slug="{% endverbatim %}{{organisation.slug}}{% verbatim %}" data-user_name="{{row.user_name}}" class="js-member-demote">
<i class="fas fa-angle-double-down"></i> {% endverbatim %}{{translate("MEMBER.DEMOTE")}}{% verbatim %}
</a>
</li>
{{ else }}
<li>
<a href="#" data-slug="{% endverbatim %}{{organisation.slug}}{% verbatim %}" data-user_name="{{row.user_name}}" class="js-member-promote">
<i class="fas fa-angle-double-up"></i> {% endverbatim %}{{translate("MEMBER.PROMOTE")}}{% verbatim %}
</a>
</li>
{{/ifx}}
<li>
<a href="#" data-slug="{% endverbatim %}{{organisation.slug}}{% verbatim %}" data-user_name="{{row.user_name}}" class="js-user-edit">
<i class="fas fa-edit"></i> {% endverbatim %}{{translate("MEMBER.EDIT")}}{% verbatim %}
</a>
</li>
<li>
<a href="#" data-slug="{% endverbatim %}{{organisation.slug}}{% verbatim %}" data-user_name="{{row.user_name}}" class="js-user-password">
<i class="fas fa-key"></i> {% endverbatim %}{{translate("MEMBER.CHANGE_PASSWORD")}}{% verbatim %}
</a>
</li>
<li>
<a href="#" data-slug="{% endverbatim %}{{organisation.slug}}{% verbatim %}" data-user_name="{{row.user_name}}" class="js-member-remove">
<i class="fas fa-door-open"></i> {% endverbatim %}{{translate("MEMBER.REMOVE")}}{% verbatim %}
</a>
</li>
{{/ifx}}
</ul>
</div>
</td>
{% endverbatim -%}
</script>
{% endif %}
{% endblock %}

View File

@@ -1,86 +0,0 @@
{% block table_cell_template_actions %}
{%- if hasRole('site-admin') or hasRole('organisations-admin') %}
<script id="{{table.id}}-column-actions" type="text/x-handlebars-template">
{%- verbatim %}
<td>
<div class="btn-group">
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown">
{% endverbatim %}{{translate("ACTIONS")}}{% verbatim %}
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right" role="menu">
{% endverbatim %}{% if checkAccess('update_organisation_field') %}{% verbatim %}
<li>
<a href="#" data-slug="{{row.slug}}" class="js-organisation-edit">
<i class="fas fa-edit"></i> {% endverbatim %}{{translate("ORGANISATION.EDIT")}}{% verbatim %}
</a>
</li>
{% endverbatim %}{% endif %}{% verbatim %}
{% endverbatim %}{% if checkAccess('merge_organisations') %}{% verbatim %}
<li>
<a href="#" data-slug="{{row.slug}}" class="js-organisation-merge">
<i class="fas fa-object-group"></i> {% endverbatim %}{{translate("ORGANISATION.MERGE")}}{% verbatim %}
</a>
</li>
{% endverbatim %}{% endif %}{% verbatim %}
{% endverbatim %}{% if checkAccess('approve_organisation') %}{% verbatim %}
{{#ifx row.flag_approved '==' 0 }}
<li>
<a href="#" data-slug="{{row.slug}}" class="js-organisation-approveRegistration">
<i class="fas fa-thumbs-up"></i> {% endverbatim %}{{translate("ORGANISATION.REGISTRATION.APPROVE")}}{% verbatim %}
</a>
</li>
<li>
<a href="#" data-slug="{{row.slug}}" class="js-organisation-denyRegistration">
<i class="fas fa-thumbs-down"></i> {% endverbatim %}{{translate("ORGANISATION.REGISTRATION.DENY")}}{% verbatim %}
</a>
</li>
{{/ifx }}
{% endverbatim %}{% endif %}{% verbatim %}
{% endverbatim %}{% if checkAccess('delete_organisation') %}{% verbatim %}
{{#ifx row.flag_approved '==' 1 }}
<li>
<a href="#" data-slug="{{row.slug}}" class="js-organisation-delete">
<i class="fas fa-trash-alt"></i> {% endverbatim %}{{translate("ORGANISATION.DELETE")}}{% verbatim %}
</a>
</li>
{{/ifx }}
{% endverbatim %}{% endif %}{% verbatim %}
</ul>
</div>
</td>
{% endverbatim -%}
</script>
{%- elseif (current_user.adminForOrganisations.count > 0) -%}
<script id="{{table.id}}-column-actions" type="text/x-handlebars-template">
{%- verbatim %}
<td>
{{#ifx row.is_admin '==' 1 }}
<div class="btn-group">
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown">
{% endverbatim %}{{translate("ACTIONS")}}{% verbatim %}
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right" role="menu">
<li>
<a href="#" data-slug="{{row.slug}}" class="js-organisation-edit">
<i class="fas fa-edit"></i> {% endverbatim %}{{translate("ORGANISATION.EDIT")}}{% verbatim %}
</a>
</li>
{{#ifx row.flag_approved '==' 0 }}
{{#ifx row.registrant_id '==' {% endverbatim %}{{current_user.id}}{% verbatim %} }}
<li>
<a href="#" data-slug="{{row.slug}}" class="js-organisation-cancelRegistration">
<i class="fas fa-trash-alt"></i> {% endverbatim %}{{translate("ORGANISATION.REGISTRATION.CANCEL")}}{% verbatim %}
</a>
</li>
{{/ifx}}
{{/ifx}}
</ul>
</div>
{{/ifx }}
</td>
{% endverbatim -%}
</script>
{% endif -%}
{% endblock %}

View File

@@ -1,11 +0,0 @@
{% block table_cell_template_admin_count %}
{%- if checkAccess('view_organisation_members') -%}
<script id="{{table.id}}-column-adminCount" type="text/x-handlebars-template">
{%- verbatim %}
<td>
{{row.admin_count}}
</td>
{% endverbatim -%}
</script>
{% endif -%}
{% endblock %}

View File

@@ -1,9 +0,0 @@
{% block table_cell_template_description %}
<script id="{{table.id}}-column-description" type="text/x-handlebars-template">
{%- verbatim %}
<td>
{{row.description}}
</td>
{% endverbatim -%}
</script>
{% endblock %}

View File

@@ -1,19 +0,0 @@
{% block table_cell_template_info %}
<script id="{{table.id}}-column-info" type="text/x-handlebars-template">
{%- verbatim %}
<td data-text="{{row.name}}">
<strong>
{{#ifx row.is_member '==' 1 }}
<a href="{{site.uri.public}}/organisations/o/{{row.slug}}">{{row.name}}</a>
{{ else }}
{% endverbatim %}{% if hasRole('organisations-admin') or hasRole('site-admin') %}{% verbatim %}
<a href="{{site.uri.public}}/organisations/o/{{row.slug}}">{{row.name}}</a>
{% endverbatim %}{% else %}{% verbatim %}
{{row.name}}
{% endverbatim %}{% endif %}{% verbatim %}
{{/ifx }}
</strong>
</td>
{% endverbatim -%}
</script>
{% endblock %}

View File

@@ -1,27 +0,0 @@
{% block table_cell_template_join %}
<script id="{{table.id}}-column-join" type="text/x-handlebars-template">
{%- verbatim %}
<td>
<div class="btn-group">
{{#ifx row.is_pending '==' 1 }}
<button type="button" data-slug="{{row.slug}}" class="btn btn-danger js-organisation-cancelJoin" style="min-width: 70px">
{% endverbatim %}{{translate("CANCEL")}}{% verbatim %}
</button>
{{ else }}
{{#ifx row.is_member '==' 1 }}
<button type="button" data-slug="{{row.slug}}" class="btn btn-danger js-organisation-leave" style="min-width: 70px">
{% endverbatim %}{{translate("LEAVE")}}{% verbatim %}
</button>
{{ else }}
{% endverbatim %}{% if (organisationConfig.membership.single_membership == 0) or ((current_user.organisations.count == 0) and (current_user.pendingOrganisations.count == 0)) %}{% verbatim %}
<button type="button" data-slug="{{row.slug}}" class="btn btn-success js-organisation-join" style="min-width: 70px">
{% endverbatim %}{{translate("JOIN")}}{% verbatim %}
</button>
{% endverbatim %}{% endif %}{% verbatim %}
{{/ifx}}
{{/ifx}}
</div>
</td>
{% endverbatim -%}
</script>
{% endblock %}

View File

@@ -1,11 +0,0 @@
{% block table_cell_template_member_count %}
{%- if checkAccess('view_organisation_members') -%}
<script id="{{table.id}}-column-memberCount" type="text/x-handlebars-template">
{%- verbatim %}
<td>
{{row.member_count}}
</td>
{% endverbatim -%}
</script>
{% endif -%}
{% endblock %}

View File

@@ -1,25 +0,0 @@
{% block table_cell_template_status %}
{%- if hasRole('site-admin') or hasRole('organisations-admin') or (current_user.adminForOrganisations.count > 0) %}
<script id="{{table.id}}-column-status" type="text/x-handlebars-template">
{%- verbatim %}
<td
{{#ifx row.flag_approved '==' 1 }}
data-text="approved"
{{ else }}
data-text="pending"
{{/ifx }}
>
{{#ifx row.flag_approved '==' 1 }}
<span>
{% endverbatim %}{{translate("APPROVED")}}{% verbatim %}
</span>
{{ else }}
<span class="text-yellow">
{% endverbatim %}{{translate("PENDING")}}{% verbatim %}
</span>
{{/ifx }}
</td>
{% endverbatim -%}
</script>
{% endif -%}
{% endblock %}

View File

@@ -1,4 +1,4 @@
{% extends "@blockier-templates/tables/users.html.twig" %} {% extends "@uf-tweaks/tables/users.html.twig" %}
{% block table %} {% block table %}
<table id="{{table.id}}" class="tablesorter table table-bordered table-hover table-striped" data-sortlist="{{table.sortlist}}"> <table id="{{table.id}}" class="tablesorter table table-bordered table-hover table-striped" data-sortlist="{{table.sortlist}}">
@@ -7,10 +7,10 @@
<th class="sorter-metatext" data-column-name="name" data-column-template="#{{table.id}}-column-info" data-priority="1">{{translate('USER')}} <i class="fas fa-sort"></i></th> <th class="sorter-metatext" data-column-name="name" data-column-template="#{{table.id}}-column-info" data-priority="1">{{translate('USER')}} <i class="fas fa-sort"></i></th>
<th class="filter-metatext" data-column-name="organisations" data-sorter="false" data-column-template="#{{table.id}}-column-organisations" data-priority="2">{{translate("ORGANISATION", 2)}}</th> <th class="filter-metatext" data-column-name="organisations" data-sorter="false" data-column-template="#{{table.id}}-column-organisations" data-priority="2">{{translate("ORGANISATION", 2)}}</th>
{% if 'last_activity' in table.columns %} {% if 'last_activity' in table.columns %}
<th class="sorter-metanum" data-column-name="last_activity" data-column-template="#{{table.id}}-column-last-activity" data-priority="3">{{translate("ACTIVITY.LAST")}} <i class="fas fa-sort"></i></th> <th class="sorter-metanum" data-column-name="last_activity" data-column-template="#{{table.id}}-column-lastActivity" data-priority="3">{{translate("ACTIVITY.LAST")}} <i class="fas fa-sort"></i></th>
{% endif %} {% endif %}
{% if 'via_roles' in table.columns %} {% if 'via_roles' in table.columns %}
<th data-column-template="#{{table.id}}-column-via-roles" data-sorter="false" data-filter="false" data-priority="1">{{translate('PERMISSION.VIA_ROLES')}}</th> <th data-column-template="#{{table.id}}-column-viaRoles" data-sorter="false" data-filter="false" data-priority="1">{{translate('PERMISSION.VIA_ROLES')}}</th>
{% endif %} {% endif %}
<th class="filter-select filter-metatext" data-column-name="status" data-column-template="#{{table.id}}-column-status" data-priority="2">{{translate("STATUS")}} <i class="fas fa-sort"></i></th> <th class="filter-select filter-metatext" data-column-name="status" data-column-template="#{{table.id}}-column-status" data-priority="2">{{translate("STATUS")}} <i class="fas fa-sort"></i></th>
<th data-column-name="actions" data-column-template="#{{table.id}}-column-actions" data-sorter="false" data-filter="false" data-priority="1">{{translate("ACTIONS")}}</th> <th data-column-name="actions" data-column-template="#{{table.id}}-column-actions" data-sorter="false" data-filter="false" data-priority="1">{{translate("ACTIONS")}}</th>
@@ -23,6 +23,9 @@
{% block table_cell_templates %} {% block table_cell_templates %}
{{ parent() }} {{ parent() }}
{{ block('table_cell_template_organisations', 'tables/partials/users/column-organisations.html.twig') }}
{% block table_users_column_organisations %}
{% include 'tables/columns/users-organisations.html.twig' %}
{% endblock %}
{% endblock %} {% endblock %}