glastree_on_gitea
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Traits;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Spatie\Permission\PermissionRegistrar;
|
||||
use Spatie\Permission\Support\Config;
|
||||
|
||||
trait HasAssignedModels
|
||||
{
|
||||
/**
|
||||
* Assign this role to the given models without removing existing assignments.
|
||||
*
|
||||
* @param Model|int|string|array<int, Model|int|string>|Collection<int, Model|int|string> $models
|
||||
* @return $this
|
||||
*/
|
||||
public function assignToModels(array|Collection|Model|int|string $models, ?string $modelClass = null): static
|
||||
{
|
||||
if (! $this->exists) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$teamPivot = $this->teamPivot();
|
||||
|
||||
foreach ($this->groupModelsByMorphClass($models, $modelClass) as $morphClass => $ids) {
|
||||
$relation = $this->relationForModel($morphClass);
|
||||
$existingIds = $relation->pluck(Config::morphKey())->all();
|
||||
|
||||
$relation->attach(array_diff($ids, $existingIds), $teamPivot);
|
||||
}
|
||||
|
||||
$this->unsetRelation('users');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove this role from the given models.
|
||||
*
|
||||
* @param Model|int|string|array<int, Model|int|string>|Collection<int, Model|int|string> $models
|
||||
* @return $this
|
||||
*/
|
||||
public function removeFromModels(array|Collection|Model|int|string $models, ?string $modelClass = null): static
|
||||
{
|
||||
foreach ($this->groupModelsByMorphClass($models, $modelClass) as $morphClass => $ids) {
|
||||
$this->relationForModel($morphClass)->detach($ids);
|
||||
}
|
||||
|
||||
$this->unsetRelation('users');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all current model associations and set the given ones.
|
||||
*
|
||||
* @param Model|int|string|array<int, Model|int|string>|Collection<int, Model|int|string> $models
|
||||
* @return $this
|
||||
*/
|
||||
public function syncModels(array|Collection|Model|int|string $models, ?string $modelClass = null): static
|
||||
{
|
||||
if ($this->exists) {
|
||||
$this->newPivotQueryForRole()->delete();
|
||||
}
|
||||
|
||||
$teamPivot = $this->teamPivot();
|
||||
|
||||
foreach ($this->groupModelsByMorphClass($models, $modelClass) as $morphClass => $ids) {
|
||||
$this->relationForModel($morphClass)->attach($ids, $teamPivot);
|
||||
}
|
||||
|
||||
$this->unsetRelation('users');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a morphedByMany relation pointing to a specific model class.
|
||||
*/
|
||||
protected function relationForModel(string $modelClass): MorphToMany
|
||||
{
|
||||
return $this->morphedByMany(
|
||||
$modelClass,
|
||||
'model',
|
||||
Config::modelHasRolesTable(),
|
||||
app(PermissionRegistrar::class)->pivotRole,
|
||||
Config::morphKey(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Group the given models by class, deduplicating IDs within each class.
|
||||
*
|
||||
* @param Model|int|string|array<int, Model|int|string>|Collection<int, Model|int|string> $models
|
||||
* @return array<class-string, list<int|string>>
|
||||
*/
|
||||
private function groupModelsByMorphClass(
|
||||
array|Collection|Model|int|string $models,
|
||||
?string $modelClass,
|
||||
): array {
|
||||
$defaultModelClass = $this->resolveDefaultModelClass($modelClass);
|
||||
|
||||
return collect(Arr::flatten(Arr::wrap($models)))
|
||||
->reject(fn ($value) => $value === null || $value === '')
|
||||
->reduce(function (array $grouped, $value) use ($defaultModelClass) {
|
||||
$class = $value instanceof Model ? $value::class : $defaultModelClass;
|
||||
$id = $value instanceof Model ? $value->getKey() : $value;
|
||||
|
||||
if (! in_array($id, $grouped[$class] ?? [], strict: true)) {
|
||||
$grouped[$class][] = $id;
|
||||
}
|
||||
|
||||
return $grouped;
|
||||
}, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the model class to use when raw IDs are passed.
|
||||
*/
|
||||
private function resolveDefaultModelClass(?string $modelClass): string
|
||||
{
|
||||
return $modelClass
|
||||
?? config('permission.models.default_model')
|
||||
?? getModelForGuard($this->attributes['guard_name'] ?? config('auth.defaults.guard'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, int|string|null>
|
||||
*/
|
||||
private function teamPivot(): array
|
||||
{
|
||||
if (! Config::teamsEnabled()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [Config::teamForeignKey() => getPermissionsTeamId()];
|
||||
}
|
||||
|
||||
private function newPivotQueryForRole(): Builder
|
||||
{
|
||||
return $this->getConnection()
|
||||
->table(Config::modelHasRolesTable())
|
||||
->where(app(PermissionRegistrar::class)->pivotRole, $this->getKey());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,577 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Traits;
|
||||
|
||||
use BackedEnum;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Spatie\Permission\Contracts\Permission;
|
||||
use Spatie\Permission\Contracts\Role;
|
||||
use Spatie\Permission\Contracts\Wildcard;
|
||||
use Spatie\Permission\Events\PermissionAttachedEvent;
|
||||
use Spatie\Permission\Events\PermissionDetachedEvent;
|
||||
use Spatie\Permission\Exceptions\GuardDoesNotMatch;
|
||||
use Spatie\Permission\Exceptions\PermissionDoesNotExist;
|
||||
use Spatie\Permission\Exceptions\WildcardPermissionInvalidArgument;
|
||||
use Spatie\Permission\Exceptions\WildcardPermissionNotImplementsContract;
|
||||
use Spatie\Permission\Guard;
|
||||
use Spatie\Permission\PermissionRegistrar;
|
||||
use Spatie\Permission\Support\Config;
|
||||
|
||||
use function Illuminate\Support\enum_value;
|
||||
|
||||
trait HasPermissions
|
||||
{
|
||||
private ?string $permissionClass = null;
|
||||
|
||||
private ?string $wildcardClass = null;
|
||||
|
||||
private array $wildcardPermissionsIndex;
|
||||
|
||||
public static function bootHasPermissions(): void
|
||||
{
|
||||
static::deleting(function ($model) {
|
||||
if (method_exists($model, 'isForceDeleting') && ! $model->isForceDeleting()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$teams = app(PermissionRegistrar::class)->teams;
|
||||
app(PermissionRegistrar::class)->teams = false;
|
||||
if (! $model instanceof Permission) {
|
||||
$model->permissions()->detach();
|
||||
}
|
||||
if ($model instanceof Role) {
|
||||
$model->users()->detach();
|
||||
}
|
||||
app(PermissionRegistrar::class)->teams = $teams;
|
||||
});
|
||||
}
|
||||
|
||||
public function getPermissionClass(): string
|
||||
{
|
||||
if (! $this->permissionClass) {
|
||||
$this->permissionClass = app(PermissionRegistrar::class)->getPermissionClass();
|
||||
}
|
||||
|
||||
return $this->permissionClass;
|
||||
}
|
||||
|
||||
public function getWildcardClass(): string
|
||||
{
|
||||
if (! is_null($this->wildcardClass)) {
|
||||
return $this->wildcardClass;
|
||||
}
|
||||
|
||||
$this->wildcardClass = '';
|
||||
|
||||
if (Config::wildcardPermissionsEnabled()) {
|
||||
$this->wildcardClass = Config::wildcardPermissionClass();
|
||||
|
||||
if (! is_subclass_of($this->wildcardClass, Wildcard::class)) {
|
||||
throw WildcardPermissionNotImplementsContract::create();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->wildcardClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* A model may have multiple direct permissions.
|
||||
*/
|
||||
public function permissions(): BelongsToMany
|
||||
{
|
||||
$relation = $this->morphToMany(
|
||||
Config::permissionModel(),
|
||||
'model',
|
||||
Config::modelHasPermissionsTable(),
|
||||
Config::morphKey(),
|
||||
app(PermissionRegistrar::class)->pivotPermission
|
||||
);
|
||||
|
||||
if (! Config::teamsEnabled()) {
|
||||
return $relation;
|
||||
}
|
||||
|
||||
$teamsKey = Config::teamForeignKey();
|
||||
$relation->withPivot($teamsKey);
|
||||
|
||||
return $relation->wherePivot($teamsKey, getPermissionsTeamId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope the model query to certain permissions only.
|
||||
*
|
||||
* @param string|int|array|Permission|Collection|BackedEnum $permissions
|
||||
*/
|
||||
public function scopePermission(Builder $query, $permissions, bool $without = false): Builder
|
||||
{
|
||||
$permissions = $this->convertToPermissionModels($permissions);
|
||||
|
||||
$permissionKey = (new ($this->getPermissionClass())())->getKeyName();
|
||||
$roleKey = (new ($this instanceof Role ? static::class : $this->getRoleClass())())->getKeyName();
|
||||
|
||||
$rolesWithPermissions = $this instanceof Role ? [] : array_unique(
|
||||
array_reduce($permissions, fn ($result, $permission) => array_merge($result, $permission->roles->all()), [])
|
||||
);
|
||||
|
||||
return $query->where(fn (Builder $query) => $query
|
||||
->{! $without ? 'whereHas' : 'whereDoesntHave'}('permissions', fn (Builder $subQuery) => $subQuery
|
||||
->whereIn(Config::permissionsTable().".$permissionKey", array_column($permissions, $permissionKey))
|
||||
)
|
||||
->when(count($rolesWithPermissions), fn ($whenQuery) => $whenQuery
|
||||
->{! $without ? 'orWhereHas' : 'whereDoesntHave'}('roles', fn (Builder $subQuery) => $subQuery
|
||||
->whereIn(Config::rolesTable().".$roleKey", array_column($rolesWithPermissions, $roleKey))
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope the model query to only those without certain permissions,
|
||||
* whether indirectly by role or by direct permission.
|
||||
*
|
||||
* @param string|int|array|Permission|Collection|BackedEnum $permissions
|
||||
*/
|
||||
public function scopeWithoutPermission(Builder $query, $permissions): Builder
|
||||
{
|
||||
return $this->scopePermission($query, $permissions, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|int|array|Permission|Collection|BackedEnum $permissions
|
||||
*
|
||||
* @throws PermissionDoesNotExist
|
||||
*/
|
||||
protected function convertToPermissionModels($permissions): array
|
||||
{
|
||||
if ($permissions instanceof Collection) {
|
||||
$permissions = $permissions->all();
|
||||
}
|
||||
|
||||
return array_map(function ($permission) {
|
||||
if ($permission instanceof Permission) {
|
||||
return $permission;
|
||||
}
|
||||
|
||||
$permission = enum_value($permission);
|
||||
|
||||
$method = is_int($permission) || PermissionRegistrar::isUid($permission) ? 'findById' : 'findByName';
|
||||
|
||||
return $this->getPermissionClass()::{$method}($permission, $this->getDefaultGuardName());
|
||||
}, Arr::wrap($permissions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a permission.
|
||||
*
|
||||
* @param string|int|Permission|BackedEnum $permission
|
||||
*
|
||||
* @throws PermissionDoesNotExist
|
||||
*/
|
||||
public function filterPermission($permission, ?string $guardName = null): Permission
|
||||
{
|
||||
$permission = enum_value($permission);
|
||||
|
||||
if (is_int($permission) || PermissionRegistrar::isUid($permission)) {
|
||||
$permission = $this->getPermissionClass()::findById(
|
||||
$permission,
|
||||
$guardName ?? $this->getDefaultGuardName()
|
||||
);
|
||||
}
|
||||
|
||||
if (is_string($permission)) {
|
||||
$permission = $this->getPermissionClass()::findByName(
|
||||
$permission,
|
||||
$guardName ?? $this->getDefaultGuardName()
|
||||
);
|
||||
}
|
||||
|
||||
if (! $permission instanceof Permission) {
|
||||
throw new PermissionDoesNotExist;
|
||||
}
|
||||
|
||||
return $permission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model may perform the given permission.
|
||||
*
|
||||
* @param string|int|Permission|BackedEnum $permission
|
||||
*
|
||||
* @throws PermissionDoesNotExist
|
||||
*/
|
||||
public function hasPermissionTo($permission, ?string $guardName = null): bool
|
||||
{
|
||||
if ($this->getWildcardClass()) {
|
||||
return $this->hasWildcardPermission($permission, $guardName);
|
||||
}
|
||||
|
||||
$permission = $this->filterPermission($permission, $guardName);
|
||||
|
||||
return $this->hasDirectPermission($permission) || $this->hasPermissionViaRole($permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a wildcard permission against all permissions of a user.
|
||||
*
|
||||
* @param string|int|Permission|BackedEnum $permission
|
||||
*/
|
||||
protected function hasWildcardPermission($permission, ?string $guardName = null): bool
|
||||
{
|
||||
$guardName = $guardName ?? $this->getDefaultGuardName();
|
||||
|
||||
$permission = enum_value($permission);
|
||||
|
||||
if (is_int($permission) || PermissionRegistrar::isUid($permission)) {
|
||||
$permission = $this->getPermissionClass()::findById($permission, $guardName);
|
||||
}
|
||||
|
||||
if ($permission instanceof Permission) {
|
||||
$guardName = $permission->guard_name ?? $guardName;
|
||||
$permission = $permission->name;
|
||||
}
|
||||
|
||||
if (! is_string($permission)) {
|
||||
throw WildcardPermissionInvalidArgument::create();
|
||||
}
|
||||
|
||||
return app($this->getWildcardClass(), ['record' => $this])->implies(
|
||||
$permission,
|
||||
$guardName,
|
||||
app(PermissionRegistrar::class)->getWildcardPermissionIndex($this),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* An alias to hasPermissionTo(), but avoids throwing an exception.
|
||||
*
|
||||
* @param string|int|Permission|BackedEnum $permission
|
||||
*/
|
||||
public function checkPermissionTo($permission, ?string $guardName = null): bool
|
||||
{
|
||||
try {
|
||||
return $this->hasPermissionTo($permission, $guardName);
|
||||
} catch (PermissionDoesNotExist $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model has any of the given permissions.
|
||||
*
|
||||
* @param string|int|array|Permission|Collection|BackedEnum ...$permissions
|
||||
*/
|
||||
public function hasAnyPermission(...$permissions): bool
|
||||
{
|
||||
$permissions = collect($permissions)->flatten();
|
||||
|
||||
foreach ($permissions as $permission) {
|
||||
if ($this->checkPermissionTo($permission)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model has all of the given permissions.
|
||||
*
|
||||
* @param string|int|array|Permission|Collection|BackedEnum ...$permissions
|
||||
*/
|
||||
public function hasAllPermissions(...$permissions): bool
|
||||
{
|
||||
$permissions = collect($permissions)->flatten();
|
||||
|
||||
foreach ($permissions as $permission) {
|
||||
if (! $this->checkPermissionTo($permission)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model has, via roles, the given permission.
|
||||
*/
|
||||
protected function hasPermissionViaRole(Permission $permission): bool
|
||||
{
|
||||
if ($this instanceof Role) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->hasRole($permission->roles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model has the given permission.
|
||||
*
|
||||
* @param string|int|Permission|BackedEnum $permission
|
||||
*
|
||||
* @throws PermissionDoesNotExist
|
||||
*/
|
||||
public function hasDirectPermission($permission): bool
|
||||
{
|
||||
$permission = $this->filterPermission($permission);
|
||||
|
||||
return $this->loadMissing('permissions')->permissions
|
||||
->contains($permission->getKeyName(), $permission->getKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the permissions the model has via roles.
|
||||
*/
|
||||
public function getPermissionsViaRoles(): Collection
|
||||
{
|
||||
if ($this instanceof Role || $this instanceof Permission) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
return $this->loadMissing('roles', 'roles.permissions')
|
||||
->roles->flatMap(fn ($role) => $role->permissions)
|
||||
->sort()->values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the permissions the model has, both directly and via roles.
|
||||
*/
|
||||
public function getAllPermissions(): Collection
|
||||
{
|
||||
/** @var Collection $permissions */
|
||||
$permissions = $this->permissions;
|
||||
|
||||
if (! $this instanceof Permission) {
|
||||
$permissions = $permissions->merge($this->getPermissionsViaRoles());
|
||||
}
|
||||
|
||||
return $permissions->sort()->values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns array of permissions ids
|
||||
*
|
||||
* @param string|int|array|Permission|Collection|BackedEnum $permissions
|
||||
*/
|
||||
private function collectPermissions(...$permissions): array
|
||||
{
|
||||
return collect($permissions)
|
||||
->flatten()
|
||||
->reduce(function ($array, $permission) {
|
||||
if ($permission === null || $permission === '') {
|
||||
return $array;
|
||||
}
|
||||
|
||||
$permission = $this->getStoredPermission($permission);
|
||||
if (! $permission instanceof Permission) {
|
||||
return $array;
|
||||
}
|
||||
|
||||
if (! in_array($permission->getKey(), $array)) {
|
||||
$this->ensureModelSharesGuard($permission);
|
||||
$array[] = $permission->getKey();
|
||||
}
|
||||
|
||||
return $array;
|
||||
}, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Grant the given permission(s) to a role.
|
||||
*
|
||||
* @param string|int|array|Permission|Collection|BackedEnum $permissions
|
||||
* @return $this
|
||||
*/
|
||||
public function givePermissionTo(...$permissions): static
|
||||
{
|
||||
$permissions = $this->collectPermissions($permissions);
|
||||
|
||||
$model = $this->getModel();
|
||||
$teamPivot = app(PermissionRegistrar::class)->teams && ! $this instanceof Role ?
|
||||
[app(PermissionRegistrar::class)->teamsKey => getPermissionsTeamId()] : [];
|
||||
|
||||
if ($model->exists) {
|
||||
$currentPermissions = $this->permissions->map(fn ($permission) => $permission->getKey())->toArray();
|
||||
|
||||
$this->permissions()->attach(array_diff($permissions, $currentPermissions), $teamPivot);
|
||||
$model->unsetRelation('permissions');
|
||||
} else {
|
||||
$class = $model::class;
|
||||
$saved = false;
|
||||
|
||||
$class::saved(
|
||||
function ($object) use ($permissions, $model, $teamPivot, &$saved) {
|
||||
if ($saved || $model->getKey() != $object->getKey()) {
|
||||
return;
|
||||
}
|
||||
$model->permissions()->attach($permissions, $teamPivot);
|
||||
$model->unsetRelation('permissions');
|
||||
$saved = true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if ($this instanceof Role) {
|
||||
$this->forgetCachedPermissions();
|
||||
}
|
||||
|
||||
if (Config::eventsEnabled()) {
|
||||
event(new PermissionAttachedEvent($this->getModel(), $permissions));
|
||||
}
|
||||
|
||||
$this->forgetWildcardPermissionIndex();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function forgetWildcardPermissionIndex(): void
|
||||
{
|
||||
app(PermissionRegistrar::class)->forgetWildcardPermissionIndex(
|
||||
$this instanceof Role ? null : $this,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all current permissions and set the given ones.
|
||||
*
|
||||
* @param string|int|array|Permission|Collection|BackedEnum $permissions
|
||||
* @return $this
|
||||
*/
|
||||
public function syncPermissions(...$permissions): static
|
||||
{
|
||||
if ($this->getModel()->exists) {
|
||||
$this->collectPermissions($permissions);
|
||||
$this->permissions()->detach();
|
||||
$this->setRelation('permissions', collect());
|
||||
}
|
||||
|
||||
return $this->givePermissionTo($permissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke the given permission(s).
|
||||
*
|
||||
* @param Permission|Permission[]|string|string[]|BackedEnum $permission
|
||||
* @return $this
|
||||
*/
|
||||
public function revokePermissionTo($permission): static
|
||||
{
|
||||
$storedPermission = $this->getStoredPermission($permission);
|
||||
|
||||
$this->permissions()->detach($storedPermission);
|
||||
|
||||
if ($this instanceof Role) {
|
||||
$this->forgetCachedPermissions();
|
||||
}
|
||||
|
||||
if (Config::eventsEnabled()) {
|
||||
event(new PermissionDetachedEvent($this->getModel(), $storedPermission));
|
||||
}
|
||||
|
||||
$this->forgetWildcardPermissionIndex();
|
||||
|
||||
$this->unsetRelation('permissions');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPermissionNames(): Collection
|
||||
{
|
||||
return $this->permissions->pluck('name');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|int|array|Permission|Collection|BackedEnum $permissions
|
||||
* @return Permission|Permission[]|Collection
|
||||
*/
|
||||
protected function getStoredPermission($permissions)
|
||||
{
|
||||
$permissions = enum_value($permissions);
|
||||
|
||||
if (is_int($permissions) || PermissionRegistrar::isUid($permissions)) {
|
||||
return $this->getPermissionClass()::findById($permissions, $this->getDefaultGuardName());
|
||||
}
|
||||
|
||||
if (is_string($permissions)) {
|
||||
return $this->getPermissionClass()::findByName($permissions, $this->getDefaultGuardName());
|
||||
}
|
||||
|
||||
if (is_array($permissions)) {
|
||||
$permissions = array_map(fn ($permission) => $permission instanceof Permission ? $permission->name : enum_value($permission), $permissions);
|
||||
|
||||
return $this->getPermissionClass()::whereIn('name', $permissions)
|
||||
->whereIn('guard_name', $this->getGuardNames())
|
||||
->get();
|
||||
}
|
||||
|
||||
return $permissions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Permission|Role $roleOrPermission
|
||||
*
|
||||
* @throws GuardDoesNotMatch
|
||||
*/
|
||||
protected function ensureModelSharesGuard($roleOrPermission): void
|
||||
{
|
||||
if (! $this->getGuardNames()->contains($roleOrPermission->guard_name)) {
|
||||
throw GuardDoesNotMatch::create($roleOrPermission->guard_name, $this->getGuardNames());
|
||||
}
|
||||
}
|
||||
|
||||
protected function getGuardNames(): Collection
|
||||
{
|
||||
return Guard::getNames($this);
|
||||
}
|
||||
|
||||
protected function getDefaultGuardName(): string
|
||||
{
|
||||
return Guard::getDefaultName($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forget the cached permissions.
|
||||
*/
|
||||
public function forgetCachedPermissions(): void
|
||||
{
|
||||
app(PermissionRegistrar::class)->forgetCachedPermissions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the model has All of the requested Direct permissions.
|
||||
*
|
||||
* @param string|int|array|Permission|Collection|BackedEnum ...$permissions
|
||||
*/
|
||||
public function hasAllDirectPermissions(...$permissions): bool
|
||||
{
|
||||
$permissions = collect($permissions)->flatten();
|
||||
|
||||
foreach ($permissions as $permission) {
|
||||
if (! $this->hasDirectPermission($permission)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the model has Any of the requested Direct permissions.
|
||||
*
|
||||
* @param string|int|array|Permission|Collection|BackedEnum ...$permissions
|
||||
*/
|
||||
public function hasAnyDirectPermission(...$permissions): bool
|
||||
{
|
||||
$permissions = collect($permissions)->flatten();
|
||||
|
||||
foreach ($permissions as $permission) {
|
||||
if ($this->hasDirectPermission($permission)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,497 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Traits;
|
||||
|
||||
use BackedEnum;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Collection;
|
||||
use Spatie\Permission\Contracts\Permission;
|
||||
use Spatie\Permission\Contracts\Role;
|
||||
use Spatie\Permission\Events\RoleAttachedEvent;
|
||||
use Spatie\Permission\Events\RoleDetachedEvent;
|
||||
use Spatie\Permission\PermissionRegistrar;
|
||||
use Spatie\Permission\Support\Config;
|
||||
use TypeError;
|
||||
|
||||
use function Illuminate\Support\enum_value;
|
||||
|
||||
trait HasRoles
|
||||
{
|
||||
use HasPermissions;
|
||||
|
||||
private ?string $roleClass = null;
|
||||
|
||||
public static function bootHasRoles(): void
|
||||
{
|
||||
static::deleting(function ($model) {
|
||||
if (method_exists($model, 'isForceDeleting') && ! $model->isForceDeleting()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$teams = app(PermissionRegistrar::class)->teams;
|
||||
app(PermissionRegistrar::class)->teams = false;
|
||||
$model->roles()->detach();
|
||||
if ($model instanceof Permission) {
|
||||
$model->users()->detach();
|
||||
}
|
||||
app(PermissionRegistrar::class)->teams = $teams;
|
||||
});
|
||||
}
|
||||
|
||||
public function getRoleClass(): string
|
||||
{
|
||||
if (! $this->roleClass) {
|
||||
$this->roleClass = app(PermissionRegistrar::class)->getRoleClass();
|
||||
}
|
||||
|
||||
return $this->roleClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* A model may have multiple roles.
|
||||
*/
|
||||
public function roles(): BelongsToMany
|
||||
{
|
||||
$relation = $this->morphToMany(
|
||||
Config::roleModel(),
|
||||
'model',
|
||||
Config::modelHasRolesTable(),
|
||||
Config::morphKey(),
|
||||
app(PermissionRegistrar::class)->pivotRole
|
||||
);
|
||||
|
||||
if (! Config::teamsEnabled()) {
|
||||
return $relation;
|
||||
}
|
||||
|
||||
$teamsKey = Config::teamForeignKey();
|
||||
$relation->withPivot($teamsKey);
|
||||
$teamField = Config::rolesTable().'.'.$teamsKey;
|
||||
|
||||
return $relation->wherePivot($teamsKey, getPermissionsTeamId())
|
||||
->where(fn ($q) => $q->whereNull($teamField)->orWhere($teamField, getPermissionsTeamId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope the model query to certain roles only.
|
||||
*
|
||||
* @param string|int|array|Role|Collection|BackedEnum $roles
|
||||
*/
|
||||
public function scopeRole(Builder $query, $roles, ?string $guard = null, bool $without = false): Builder
|
||||
{
|
||||
if ($roles instanceof Collection) {
|
||||
$roles = $roles->all();
|
||||
}
|
||||
|
||||
$roles = array_map(function ($role) use ($guard) {
|
||||
if ($role instanceof Role) {
|
||||
return $role;
|
||||
}
|
||||
|
||||
$role = enum_value($role);
|
||||
|
||||
$method = is_int($role) || PermissionRegistrar::isUid($role) ? 'findById' : 'findByName';
|
||||
|
||||
return $this->getRoleClass()::{$method}($role, $guard ?: $this->getDefaultGuardName());
|
||||
}, Arr::wrap($roles));
|
||||
|
||||
$key = (new ($this->getRoleClass())())->getKeyName();
|
||||
|
||||
return $query->{! $without ? 'whereHas' : 'whereDoesntHave'}('roles', fn (Builder $subQuery) => $subQuery
|
||||
->whereIn(Config::rolesTable().".$key", array_column($roles, $key))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope the model query to only those without certain roles.
|
||||
*
|
||||
* @param string|int|array|Role|Collection|BackedEnum $roles
|
||||
*/
|
||||
public function scopeWithoutRole(Builder $query, $roles, ?string $guard = null): Builder
|
||||
{
|
||||
return $this->scopeRole($query, $roles, $guard, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* A model may be part of multiple teams.
|
||||
*
|
||||
* When the teams feature is disabled this returns an empty BelongsToMany so
|
||||
* tooling that introspects model relations (e.g. ide-helper:models) does not
|
||||
* break. Querying it is a no-op and produces no rows.
|
||||
*/
|
||||
public function teams(): BelongsToMany
|
||||
{
|
||||
if (! Config::teamsEnabled()) {
|
||||
return $this->morphToMany(
|
||||
Config::permissionModel(),
|
||||
'model',
|
||||
Config::modelHasRolesTable(),
|
||||
Config::morphKey(),
|
||||
Config::teamForeignKey()
|
||||
)->whereRaw('1 = 0');
|
||||
}
|
||||
|
||||
return $this->morphToMany(
|
||||
Config::teamModel(),
|
||||
'model',
|
||||
Config::modelHasRolesTable(),
|
||||
Config::morphKey(),
|
||||
Config::teamForeignKey()
|
||||
)->distinct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope the model query to certain teams only.
|
||||
*
|
||||
* @param int|string|array|Model|Collection $teams
|
||||
*/
|
||||
public function scopeTeam(Builder $query, $teams, bool $without = false): Builder
|
||||
{
|
||||
$teamModel = Config::teamModel();
|
||||
|
||||
if ($teams instanceof Collection) {
|
||||
$teams = $teams->all();
|
||||
}
|
||||
|
||||
$teamIds = array_map(
|
||||
fn ($team) => $team instanceof $teamModel ? $team->getKey() : $team,
|
||||
Arr::wrap($teams),
|
||||
);
|
||||
|
||||
$pivotTable = Config::modelHasRolesTable();
|
||||
$morphKey = Config::morphKey();
|
||||
$teamsKey = Config::teamForeignKey();
|
||||
|
||||
return $query->{! $without ? 'whereExists' : 'whereNotExists'}(
|
||||
fn ($subQuery) => $subQuery
|
||||
->from($pivotTable)
|
||||
->whereColumn($morphKey, $query->getModel()->getQualifiedKeyName())
|
||||
->where('model_type', $query->getModel()->getMorphClass())
|
||||
->whereIn($teamsKey, $teamIds)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope the model query to those without certain teams.
|
||||
*
|
||||
* @param int|string|array|Model|Collection $teams
|
||||
*/
|
||||
public function scopeWithoutTeam(Builder $query, $teams): Builder
|
||||
{
|
||||
return $this->scopeTeam($query, $teams, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns array of role ids
|
||||
*
|
||||
* @param string|int|array|Role|Collection|BackedEnum $roles
|
||||
*/
|
||||
private function collectRoles(...$roles): array
|
||||
{
|
||||
return collect($roles)
|
||||
->flatten()
|
||||
->reduce(function ($array, $role) {
|
||||
if ($role === null || $role === '') {
|
||||
return $array;
|
||||
}
|
||||
|
||||
$role = $this->getStoredRole($role);
|
||||
|
||||
if (! in_array($role->getKey(), $array)) {
|
||||
$this->ensureModelSharesGuard($role);
|
||||
$array[] = $role->getKey();
|
||||
}
|
||||
|
||||
return $array;
|
||||
}, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign the given role to the model.
|
||||
*
|
||||
* @param string|int|array|Role|Collection|BackedEnum ...$roles
|
||||
* @return $this
|
||||
*/
|
||||
public function assignRole(...$roles): static
|
||||
{
|
||||
$roles = $this->collectRoles($roles);
|
||||
|
||||
$model = $this->getModel();
|
||||
$teamPivot = app(PermissionRegistrar::class)->teams && ! $this instanceof Permission ?
|
||||
[app(PermissionRegistrar::class)->teamsKey => getPermissionsTeamId()] : [];
|
||||
|
||||
if ($model->exists) {
|
||||
if (app(PermissionRegistrar::class)->teams) {
|
||||
// explicit reload in case team has been changed since last load
|
||||
$this->load('roles');
|
||||
}
|
||||
|
||||
$currentRoles = $this->roles->map(fn ($role) => $role->getKey())->toArray();
|
||||
|
||||
$this->roles()->attach(array_diff($roles, $currentRoles), $teamPivot);
|
||||
$model->unsetRelation('roles');
|
||||
} else {
|
||||
$class = $model::class;
|
||||
$saved = false;
|
||||
|
||||
$class::saved(
|
||||
function ($object) use ($roles, $model, $teamPivot, &$saved) {
|
||||
if ($saved || $model->getKey() != $object->getKey()) {
|
||||
return;
|
||||
}
|
||||
$model->roles()->attach($roles, $teamPivot);
|
||||
$model->unsetRelation('roles');
|
||||
$saved = true;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if ($this instanceof Permission) {
|
||||
$this->forgetCachedPermissions();
|
||||
}
|
||||
|
||||
$this->forgetWildcardPermissionIndex();
|
||||
|
||||
if (Config::eventsEnabled()) {
|
||||
event(new RoleAttachedEvent($this->getModel(), $roles));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke the given role from the model.
|
||||
*
|
||||
* @param string|int|array|Role|Collection|BackedEnum ...$role
|
||||
* @return $this
|
||||
*/
|
||||
public function removeRole(...$role): static
|
||||
{
|
||||
$roles = $this->collectRoles($role);
|
||||
|
||||
$this->roles()->detach($roles);
|
||||
|
||||
$this->unsetRelation('roles');
|
||||
|
||||
if ($this instanceof Permission) {
|
||||
$this->forgetCachedPermissions();
|
||||
}
|
||||
|
||||
$this->forgetWildcardPermissionIndex();
|
||||
|
||||
if (Config::eventsEnabled()) {
|
||||
event(new RoleDetachedEvent($this->getModel(), $roles));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all current roles and set the given ones.
|
||||
*
|
||||
* @param string|int|array|Role|Collection|BackedEnum ...$roles
|
||||
* @return $this
|
||||
*/
|
||||
public function syncRoles(...$roles): static
|
||||
{
|
||||
if ($this->getModel()->exists) {
|
||||
$this->collectRoles($roles);
|
||||
if (Config::eventsEnabled()) {
|
||||
$currentRoles = $this->roles()->get();
|
||||
if ($currentRoles->isNotEmpty()) {
|
||||
$this->removeRole($currentRoles);
|
||||
}
|
||||
} else {
|
||||
$this->roles()->detach();
|
||||
$this->setRelation('roles', collect());
|
||||
}
|
||||
}
|
||||
|
||||
return $this->assignRole($roles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model has (one of) the given role(s).
|
||||
*
|
||||
* @param string|int|array|Role|Collection|BackedEnum $roles
|
||||
*/
|
||||
public function hasRole($roles, ?string $guard = null): bool
|
||||
{
|
||||
$this->loadMissing('roles');
|
||||
|
||||
if (is_string($roles) && str_contains($roles, '|')) {
|
||||
$roles = $this->convertPipeToArray($roles);
|
||||
}
|
||||
|
||||
if ($roles instanceof BackedEnum) {
|
||||
$roles = $roles->value;
|
||||
|
||||
return $this->roles
|
||||
->when($guard, fn ($q) => $q->where('guard_name', $guard))
|
||||
->pluck('name')
|
||||
->contains(fn ($name) => enum_value($name) == $roles);
|
||||
}
|
||||
|
||||
if (is_int($roles) || PermissionRegistrar::isUid($roles)) {
|
||||
$key = (new ($this->getRoleClass())())->getKeyName();
|
||||
|
||||
return $guard
|
||||
? $this->roles->where('guard_name', $guard)->contains($key, $roles)
|
||||
: $this->roles->contains($key, $roles);
|
||||
}
|
||||
|
||||
if (is_string($roles)) {
|
||||
return $guard
|
||||
? $this->roles->where('guard_name', $guard)->contains('name', $roles)
|
||||
: $this->roles->contains('name', $roles);
|
||||
}
|
||||
|
||||
if ($roles instanceof Role) {
|
||||
return $this->roles->contains($roles->getKeyName(), $roles->getKey());
|
||||
}
|
||||
|
||||
if (is_array($roles)) {
|
||||
foreach ($roles as $role) {
|
||||
if ($this->hasRole($role, $guard)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($roles instanceof Collection) {
|
||||
return $roles->intersect($guard ? $this->roles->where('guard_name', $guard) : $this->roles)->isNotEmpty();
|
||||
}
|
||||
|
||||
throw new TypeError('Unsupported type for $roles parameter to hasRole().');
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model has any of the given role(s).
|
||||
*
|
||||
* Alias to hasRole() but without Guard controls
|
||||
*
|
||||
* @param string|int|array|Role|Collection|BackedEnum $roles
|
||||
*/
|
||||
public function hasAnyRole(...$roles): bool
|
||||
{
|
||||
return $this->hasRole($roles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model has all of the given role(s).
|
||||
*
|
||||
* @param string|array|Role|Collection|BackedEnum $roles
|
||||
*/
|
||||
public function hasAllRoles($roles, ?string $guard = null): bool
|
||||
{
|
||||
$this->loadMissing('roles');
|
||||
|
||||
$roles = enum_value($roles);
|
||||
|
||||
if (is_string($roles) && str_contains($roles, '|')) {
|
||||
$roles = $this->convertPipeToArray($roles);
|
||||
}
|
||||
|
||||
if (is_string($roles)) {
|
||||
return $this->hasRole($roles, $guard);
|
||||
}
|
||||
|
||||
if ($roles instanceof Role) {
|
||||
return $this->roles->contains($roles->getKeyName(), $roles->getKey());
|
||||
}
|
||||
|
||||
$roles = collect()->make($roles)->map(fn ($role) => $role instanceof Role ? $role->name : enum_value($role));
|
||||
|
||||
$roleNames = $guard
|
||||
? $this->roles->where('guard_name', $guard)->pluck('name')
|
||||
: $this->getRoleNames();
|
||||
|
||||
$roleNames = $roleNames->transform(fn ($roleName) => enum_value($roleName));
|
||||
|
||||
return $roles->intersect($roleNames) == $roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the model has exactly all of the given role(s).
|
||||
*
|
||||
* @param string|array|Role|Collection|BackedEnum $roles
|
||||
*/
|
||||
public function hasExactRoles($roles, ?string $guard = null): bool
|
||||
{
|
||||
$this->loadMissing('roles');
|
||||
|
||||
if (is_string($roles) && str_contains($roles, '|')) {
|
||||
$roles = $this->convertPipeToArray($roles);
|
||||
}
|
||||
|
||||
if (is_string($roles)) {
|
||||
$roles = [$roles];
|
||||
}
|
||||
|
||||
if ($roles instanceof Role) {
|
||||
$roles = [$roles->name];
|
||||
}
|
||||
|
||||
$roles = collect()->make($roles)->map(fn ($role) => $role instanceof Role ? $role->name : $role
|
||||
);
|
||||
|
||||
return $this->roles->count() == $roles->count() && $this->hasAllRoles($roles, $guard);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all permissions directly coupled to the model.
|
||||
*/
|
||||
public function getDirectPermissions(): Collection
|
||||
{
|
||||
return $this->permissions;
|
||||
}
|
||||
|
||||
public function getRoleNames(): Collection
|
||||
{
|
||||
$this->loadMissing('roles');
|
||||
|
||||
return $this->roles->pluck('name');
|
||||
}
|
||||
|
||||
protected function getStoredRole($role): Role
|
||||
{
|
||||
$role = enum_value($role);
|
||||
|
||||
if (is_int($role) || PermissionRegistrar::isUid($role)) {
|
||||
return $this->getRoleClass()::findById($role, $this->getDefaultGuardName());
|
||||
}
|
||||
|
||||
if (is_string($role)) {
|
||||
return $this->getRoleClass()::findByName($role, $this->getDefaultGuardName());
|
||||
}
|
||||
|
||||
return $role;
|
||||
}
|
||||
|
||||
protected function convertPipeToArray(string $pipeString): array
|
||||
{
|
||||
$pipeString = trim($pipeString);
|
||||
|
||||
if (strlen($pipeString) <= 2) {
|
||||
return [str_replace('|', '', $pipeString)];
|
||||
}
|
||||
|
||||
$quoteCharacter = substr($pipeString, 0, 1);
|
||||
$endCharacter = substr($quoteCharacter, -1, 1);
|
||||
|
||||
if ($quoteCharacter !== $endCharacter) {
|
||||
return explode('|', $pipeString);
|
||||
}
|
||||
|
||||
if (! in_array($quoteCharacter, ["'", '"'])) {
|
||||
return explode('|', $pipeString);
|
||||
}
|
||||
|
||||
return explode('|', trim($pipeString, $quoteCharacter));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Spatie\Permission\Traits;
|
||||
|
||||
use Spatie\Permission\PermissionRegistrar;
|
||||
|
||||
trait RefreshesPermissionCache
|
||||
{
|
||||
public static function bootRefreshesPermissionCache()
|
||||
{
|
||||
static::saved(function () {
|
||||
app(PermissionRegistrar::class)->forgetCachedPermissions();
|
||||
});
|
||||
|
||||
static::deleted(function () {
|
||||
app(PermissionRegistrar::class)->forgetCachedPermissions();
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user