Allow users to see their own organisation pages

This commit is contained in:
2022-02-08 16:13:43 +00:00
parent 2d701760c3
commit b3c51527a0
9 changed files with 275 additions and 6 deletions

View File

@@ -1,5 +1,20 @@
{
"bundle": {
"js/admin": {
"scripts": [
"vendor/moment/moment.js",
"userfrosting/js/handlebars-helpers.js",
"vendor/tablesorter/dist/js/jquery.tablesorter.js",
"vendor/tablesorter/dist/js/jquery.tablesorter.widgets.js",
"userfrosting/js/tablesorter/widget-sort2Hash.js",
"vendor/tablesorter/dist/js/widgets/widget-columnSelector.min.js",
"vendor/tablesorter/dist/js/widgets/widget-reflow.min.js",
"vendor/tablesorter/dist/js/widgets/widget-pager.min.js",
"userfrosting/js/query-string.js",
"userfrosting/js/uf-table.js",
"avsdev/js/sidebar.js"
]
},
"js/pages/organisation": {
"scripts": [
"userfrosting/js/widgets/users.js",

View File

@@ -0,0 +1,23 @@
$(document).ready(function() {
$('[data-toggle="tooltip"]').tooltip();
$('.sidebar-menu .collapse, .sidebar-menu .collapsing').on('hide.bs.collapse', function() {
$(this).prev().find('.fa').eq(1).removeClass('fa-angle-right').addClass('fa-angle-down');
var states = JSON.parse(localStorage.getItem('sidebar-states') || {});
states[this.id] = 0;
localStorage.setItem('sidebar-states', JSON.stringify(states));
});
$('.sidebar-menu .collapse, sidebar-menu .collapsing').on('show.bs.collapse', function() {
$(this).prev().find('.fa').eq(1).removeClass('fa-angle-down').addClass('fa-angle-right');
var states = JSON.parse(localStorage.getItem('sidebar-states') || {});
states[this.id] = 1;
localStorage.setItem('sidebar-states', JSON.stringify(states));
});
var states = JSON.parse(localStorage.getItem('sidebar-states') || {});
Object.getOwnPropertyNames(states).forEach((elid) => {
if (states[elid] === 1) {
$('#' + elid).collapse('show');
}
});
})

View File

@@ -36,9 +36,11 @@ return [
'DELETE_YES' => 'Yes, delete organisation',
'DELETION_SUCCESSFUL' => 'Successfully deleted organisation <strong>{{name}}</strong>',
'MEMBER_COUNT' => '# Members <sub>(excl admins)</sub>',
'MEMBER_COUNT' => '# Members <sub>(excl admins)</sub>',
'ADMIN_COUNT' => '# Admins',
'SELF' => 'My Organisations',
'NAME' => [
1 => 'Organisation name',

View File

@@ -74,10 +74,7 @@ class OrganisationMembersController extends SimpleController
$sprunje = $classMapper->createInstance('user_sprunje', $classMapper, $params);
$sprunje->extendQuery(function ($query) use ($classMapper, $organisation) {
return $query
->join('organisation_members', function($join) use($organisation) {
$join->on('organisation_members.user_id', '=', 'users.id')->where('organisation_id', $organisation->id);
});
return $query->where('organisation_id', $organisation->id);
});
// Be careful how you consume this data - it has not been escaped and contains untrusted user-supplied content.

View File

@@ -0,0 +1,76 @@
<?php
/*
* AVSDev UF Organisations (https://avsdev.uk)
*
* @link https://git.avsdev.uk/avsdev/sprinkle-organisations
* @license https://git.avsdev.uk/avsdev/sprinkle-organisations/blob/master/LICENSE.md (LGPL-3.0 License)
*/
namespace UserFrosting\Sprinkle\Organisations\Database\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\SoftDeletes;
use UserFrosting\Sprinkle\Account\Database\Models\Interfaces\UserInterface;
use UserFrosting\Sprinkle\Account\Database\Models\User as UFUser;
use UserFrosting\Sprinkle\Account\Facades\Password;
use UserFrosting\Sprinkle\Core\Database\Models\Model;
use UserFrosting\Sprinkle\Core\Facades\Debug;
/**
* User Class.
*
* Extends the UF User object by adding organisation membership
*
* @author Craig Williams (https://avsdev.uk)
*/
class User extends UFUser
{
/**
* Get all organisations this user is a member of.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function organisations()
{
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = static::$ci->classMapper;
return $this->belongsToMany($classMapper->getClassMapping('organisation'), 'organisation_members', 'user_id', 'organisation_id');
}
/**
* Delete this member from the database, along with any links to organisations.
*
* @param bool $hardDelete Set to true to completely remove the member and all associated objects.
*
* @return bool true if the deletion was successful, false otherwise.
*/
public function delete($hardDelete = false)
{
/** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
$classMapper = static::$ci->classMapper;
if ($hardDelete) {
// TODO: remove user from organisations?
}
return parent::delete();
}
/**
* Joins the user's organisations directly, so we can do things like sort, search, paginate, etc.
*
* @param Builder $query
*
* @return Builder
*/
public function scopeJoinOrganisations($query)
{
$query = $query->leftJoin('organisation_members', 'organisation_members.user_id', '=', 'users.id');
$query = $query->leftJoin('organisations', 'organisations.id', '=', 'organisation_members.organisation_id');
return $query;
}
}

View File

@@ -27,6 +27,7 @@ class OrganisationPermissions extends BaseSeed
public function run()
{
// We require the default roles
Seeder::execute('DefaultPermissions');
Seeder::execute('DefaultRoles');
Seeder::execute('OrganisationRoles');
@@ -56,6 +57,12 @@ class OrganisationPermissions extends BaseSeed
'conditions' => "in(property,['name','slug','description','members'])",
'description' => 'View certain properties of any organisation.',
]),
'view_organisation_field_own' => new Permission([
'slug' => 'view_organisation_field',
'name' => 'View own organisation',
'conditions' => "is_organisation_member(self.id,organisation.id) & in(property,['name','slug','description','members'])",
'description' => 'View certain properties of own organisation.',
]),
'update_organisation_field' => new Permission([
'slug' => 'update_organisation_field',
'name' => 'Edit organisation',
@@ -80,6 +87,12 @@ class OrganisationPermissions extends BaseSeed
'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',
@@ -144,6 +157,13 @@ class OrganisationPermissions extends BaseSeed
$permissions['uri_organisation']->id,
]);
}
$roleUser = Role::where('slug', 'user')->first();
if ($roleUser) {
$roleUser->permissions()->syncWithoutDetaching([
$permissions['uri_organisation_own']->id,
$permissions['view_organisation_field_own']->id,
]);
}
}
}

View File

@@ -9,6 +9,7 @@
namespace UserFrosting\Sprinkle\Organisations\ServicesProvider;
use Illuminate\Database\Capsule\Manager as Capsule;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
@@ -38,10 +39,49 @@ class ServicesProvider
$container->extend('classMapper', function ($classMapper, $c) {
$classMapper->setClassMapping('organisation', 'UserFrosting\Sprinkle\Organisations\Database\Models\Organisation');
$classMapper->setClassMapping('organisation_sprunje', 'UserFrosting\Sprinkle\Organisations\Sprunje\OrganisationSprunje');
$classMapper->setClassMapping('user', 'UserFrosting\Sprinkle\Organisations\Database\Models\User');
$classMapper->setClassMapping('user_sprunje', 'UserFrosting\Sprinkle\Organisations\Sprunje\UserSprunje');
return $classMapper;
});
/*
* Extend the 'authorizer' service to add extra access condition callbacks.
*
* @return \UserFrosting\Sprinkle\Core\Util\ClassMapper
*/
$container->extend('authorizer', function ($authorizer, $c) {
/*
* Check if all $user is a member of $organisation.
*
* @param int $user_id the id of the requesting user (normally currentUser->id).
* @param int $organisation_id the id of the target organisation.
* @return bool true if $user is a member of $organisation.
*/
$authorizer->addCallback('is_organisation_member', function ($user_id, $organisation_id) {
return Capsule::table('organisation_members')
->where('user_id', $user_id)
->where('organisation_id', $organisation_id)
->count() > 0;
});
/*
* Check if all $user is an administrator of $organisation.
*
* @param int $user_id the id of the requesting user (normally currentUser->id).
* @param int $organisation_id the id of the target organisation.
* @return bool true if $user is an administrator of $organisation.
*/
$authorizer->addCallback('is_organisation_admin', function ($user_id, $organisation_id) {
return Capsule::table('organisation_members')
->where('user_id', $user_id)
->where('organisation_id', $organisation_id)
->where('flag_admin', true)
->count() > 0;
});
return $authorizer;
});
/*
* Returns a callback that handles merging any organisation objects.
@@ -79,6 +119,5 @@ class ServicesProvider
};
};
}
}

View File

@@ -0,0 +1,85 @@
<?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\Sprunje;
use Illuminate\Database\Schema\Builder;
use UserFrosting\Sprinkle\Core\Facades\Translator;
use UserFrosting\Sprinkle\Core\Sprunje\Sprunje;
use UserFrosting\Sprinkle\Admin\Sprunje\UserSprunje as UFUserSprunje;
/**
* UserSprunje.
*
* Extends Sprunje for the users API.
*
* @author Craig Williams (https://avsdev.uk)
*/
class UserSprunje extends UFUserSprunje
{
protected $filterable = [
'name',
'organisations',
'last_activity',
'status',
];
protected $sortable = [
'name',
'organisations',
'last_activity',
'status',
];
/**
* {@inheritdoc}
*/
protected function baseQuery()
{
$query = parent::baseQuery();
// Join user's organisations
return $query->joinOrganisations()->with('organisations');
}
/**
* Sort based on organisations names.
*
* @param Builder $query
* @param mixed $value
*
* @return self
*/
protected function sortOrganisations($query, $direction)
{
$query->orderBy('organisations.name', $direction);
return $this;
}
/**
* Filter LIKE the last activity description.
*
* @param Builder $query
* @param mixed $value
*
* @return self
*/
protected function filterOrganisations($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->orLike('organisations.name', $value);
}
});
return $this;
}
}

View File

@@ -40,4 +40,16 @@
<a href="{{site.uri.public}}/organisations"><i class="fas fa-users fa-fw"></i> <span>{{ translate("ORGANISATION", 2) }}</span></a>
</li>
{% endif %}
{% if current_user.organisations.count() > 0 %}
<li>
<a href="#" data-toggle="collapse" data-target="#submenu-organisations" class="" aria-expanded="false"><i class="fa fa-fw fa-users"></i> {{ translate("ORGANISATION.SELF") }} <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 %}
<li>
<a href="{{site.uri.public}}/organisations/o/{{organisation.slug}}"><i class="fas fa-angle-double-right"></i> <span>{{organisation.name}}</span></a>
</li>
{% endfor %}
</ul>
</li>
{% endif %}
{% endblock %}