BF joints

This commit is contained in:
Fabian @ Blax Software 2026-02-10 15:22:26 +01:00
parent 5a843191b9
commit 62484f9307
2 changed files with 82 additions and 32 deletions

View File

@ -20,22 +20,25 @@ class Permission extends Model
public function usages() public function usages()
{ {
return $this->hasMany(config('roles.table_names.permission_usage')); return $this->hasMany(config('roles.models.permission_usage'));
} }
/**
* Get all roles that have this permission (via permission_members where member_type is Role).
*/
public function roles() public function roles()
{ {
return $this->morphToMany( return $this->morphedByMany(
config('roles.table_names.role'), config('roles.models.role'),
'member', 'member',
config('roles.table_names.permission_member'), config('roles.table_names.permission_member', 'permission_members'),
'permission_id', 'permission_id',
'member_id' 'member_id'
)->where('member_type', config('roles.table_names.role')); );
} }
public function members() public function members()
{ {
return $this->hasMany(config('roles.table_names.permission_member')); return $this->hasMany(config('roles.models.permission_member'), 'permission_id');
} }
} }

View File

@ -3,46 +3,101 @@
namespace Blax\Roles\Traits; namespace Blax\Roles\Traits;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
trait HasPermissions trait HasPermissions
{ {
/**
* Check if the entity has a specific permission.
*
* Supports hierarchical matching: having permission 'lection' also
* grants 'lection.45', 'lection.foo.bar', etc. (parent acts as wildcard).
*/
public function hasPermission(string $permission): bool public function hasPermission(string $permission): bool
{ {
$allpermissions = $this->permissions(); $allpermissions = $this->permissions();
// Wildcard: '*' grants everything
if ($allpermissions->contains('slug', '*')) { if ($allpermissions->contains('slug', '*')) {
return true; // If any permission is '*', all permissions are granted return true;
} }
return $allpermissions->contains(function ($perm) use ($permission) { return $allpermissions->contains(function ($perm) use ($permission) {
return $perm->slug === $permission || $perm->name === $permission; // Exact match
if ($perm->slug === $permission) {
return true;
}
// Hierarchical: permission 'lection' grants 'lection.45', 'lection.foo.bar', etc.
if (str_starts_with($permission, $perm->slug . '.')) {
return true;
}
return false;
}); });
} }
public function rolePermissions() /**
* Get permissions inherited through roles.
*
* Resolves: entity role_members (get role IDs) permission_members
* where member_type is a Role permissions.
*/
public function rolePermissions(): Collection
{ {
return $this->hasManyThrough( $roleModel = config('roles.models.role');
config('roles.models.permission'), $permissionModel = config('roles.models.permission');
config('roles.models.role_member'), $roleMemberTable = config('roles.table_names.role_member', 'role_members');
'member_id', $permMemberTable = config('roles.table_names.permission_member', 'permission_members');
'id',
'id', // Get role IDs this entity belongs to (via role_members)
'role_id' $roleIds = DB::table($roleMemberTable)
); ->where('member_id', $this->getKey())
->where('member_type', $this->getMorphClass())
->where(function ($q) {
$q->whereNull('expires_at')->orWhere('expires_at', '>', now());
})
->pluck('role_id');
if ($roleIds->isEmpty()) {
return collect();
}
// Get permission IDs assigned to those roles (roles are members in permission_members)
$permissionIds = DB::table($permMemberTable)
->whereIn('member_id', $roleIds)
->where('member_type', $roleModel)
->where(function ($q) {
$q->whereNull('expires_at')->orWhere('expires_at', '>', now());
})
->pluck('permission_id')
->unique();
if ($permissionIds->isEmpty()) {
return collect();
}
return $permissionModel::whereIn('id', $permissionIds)->get();
} }
/**
* Get permissions directly assigned to this entity (via permission_members morphToMany).
*/
public function individualPermissions() public function individualPermissions()
{ {
return $this->morphToMany( return $this->morphToMany(
config('roles.models.permission'), config('roles.models.permission'),
'member', 'member',
config('roles.table_names.permission_member', 'permission_member') config('roles.table_names.permission_member', 'permission_members')
); );
} }
public function permissions() /**
* Get all permissions: role-based + directly assigned, deduplicated.
*/
public function permissions(): Collection
{ {
$rolePerms = $this->rolePermissions()->get(); $rolePerms = $this->rolePermissions();
$directPerms = $this->individualPermissions()->get(); $directPerms = $this->individualPermissions()->get();
return $rolePerms return $rolePerms
@ -62,19 +117,13 @@ trait HasPermissions
]); ]);
} }
if ($permission instanceof $permission_class) { if (! ($permission instanceof $permission_class)) {
// Already a Permission instance
} else {
throw new \InvalidArgumentException('Permission must be a string, numeric ID, or an instance of Permission.'); throw new \InvalidArgumentException('Permission must be a string, numeric ID, or an instance of Permission.');
} }
if ($permission) { $this->individualPermissions()->syncWithoutDetaching($permission);
$this->permissions()->syncWithoutDetaching($permission);
return true; return true;
}
return false;
} }
public function removePermission($permission): bool public function removePermission($permission): bool
@ -88,9 +137,7 @@ trait HasPermissions
} }
if ($permission) { if ($permission) {
$this->permissions()->detach($permission); $this->individualPermissions()->detach($permission);
return true;
} }
return true; return true;