165 lines
5.2 KiB
PHP
165 lines
5.2 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Admin;
|
|
|
|
use App\Models\Operator;
|
|
use Illuminate\Support\Facades\Gate;
|
|
use Livewire\Attributes\Layout;
|
|
use Livewire\Component;
|
|
use Spatie\Permission\Models\Permission;
|
|
use Spatie\Permission\Models\Role;
|
|
use Spatie\Permission\PermissionRegistrar;
|
|
|
|
/**
|
|
* Wer was darf — als Matrix, nicht als Migration.
|
|
*
|
|
* Bis hierher standen die Rechte einer Rolle in Migrationen und die Liste der
|
|
* zulässigen Rollennamen an DREI Stellen im Code. Das ist kein Schönheitsfehler
|
|
* gewesen: `Developer` existierte als Rolle und wurde vom Einladen-Formular nie
|
|
* angeboten, weil eine der drei Listen nicht mitgezogen worden war.
|
|
*
|
|
* Deshalb kommt hier alles aus der Datenbank. Eine Rolle, die es gibt, steht
|
|
* auf diesem Bildschirm — und was auf diesem Bildschirm steht, gilt.
|
|
*
|
|
* ZWEI Grenzen, beide mit Grund:
|
|
*
|
|
* - **Owner ist unantastbar.** Ein Klick, der dem Owner Rechte nimmt, sperrt
|
|
* den Betreiber aus seiner eigenen Installation aus, und es gibt niemanden
|
|
* mehr, der es zurücknehmen könnte.
|
|
* - **Alles läuft auf dem `operator`-Guard.** Genau diese Verwechslung hat
|
|
* `dpa.manage` zusätzlich auf `web` hinterlassen, wo es kein Betreiber je
|
|
* sieht — Operator löst gegen `operator` auf (siehe dessen `$guard_name`).
|
|
*/
|
|
#[Layout('layouts.admin')]
|
|
class Roles extends Component
|
|
{
|
|
public const GUARD = 'operator';
|
|
|
|
/** @var array<int, array<int, string>> Rollen-ID => angehakte Berechtigungen */
|
|
public array $granted = [];
|
|
|
|
public string $newRole = '';
|
|
|
|
public function mount(): void
|
|
{
|
|
abort_unless(Gate::allows('staff.manage'), 403);
|
|
|
|
$this->loadGrants();
|
|
}
|
|
|
|
public function save(int $roleId): void
|
|
{
|
|
$this->authorize('staff.manage');
|
|
|
|
$role = $this->role($roleId);
|
|
|
|
if ($role->name === 'Owner') {
|
|
$this->addError('granted.'.$roleId, __('roles.owner_locked'));
|
|
|
|
return;
|
|
}
|
|
|
|
// Nur bekannte Namen: die Liste kommt aus dem Browser.
|
|
$wanted = array_values(array_intersect(
|
|
$this->granted[$roleId] ?? [],
|
|
$this->allPermissions(),
|
|
));
|
|
|
|
$role->syncPermissions($wanted);
|
|
|
|
// Spatie hält die Rechte in einem Zwischenspeicher. Ohne das hier
|
|
// gölte die Änderung erst, wenn der Zwischenspeicher von selbst
|
|
// abläuft — und die Seite zeigte währenddessen das Neue an, während
|
|
// das Alte wirkt.
|
|
app(PermissionRegistrar::class)->forgetCachedPermissions();
|
|
|
|
$this->loadGrants();
|
|
$this->dispatch('notify', message: __('roles.saved', ['role' => $role->name]));
|
|
}
|
|
|
|
public function create(): void
|
|
{
|
|
$this->authorize('staff.manage');
|
|
|
|
$this->validate(['newRole' => ['required', 'string', 'max:64', 'regex:/^[\pL][\pL\pN \-]*$/u']]);
|
|
|
|
Role::findOrCreate(trim($this->newRole), self::GUARD);
|
|
app(PermissionRegistrar::class)->forgetCachedPermissions();
|
|
|
|
$this->newRole = '';
|
|
$this->loadGrants();
|
|
$this->dispatch('notify', message: __('roles.created'));
|
|
}
|
|
|
|
public function delete(int $roleId): void
|
|
{
|
|
$this->authorize('staff.manage');
|
|
|
|
$role = $this->role($roleId);
|
|
|
|
if ($role->name === 'Owner') {
|
|
$this->addError('newRole', __('roles.owner_locked'));
|
|
|
|
return;
|
|
}
|
|
|
|
// Eine Rolle zu löschen, die jemand hält, nähme diesem Menschen
|
|
// wortlos jeden Zugang — er stünde beim nächsten Anmelden ohne Rolle
|
|
// da und käme in die Konsole nicht mehr hinein.
|
|
if ($role->users()->exists()) {
|
|
$this->addError('newRole', __('roles.in_use', ['role' => $role->name]));
|
|
|
|
return;
|
|
}
|
|
|
|
$role->delete();
|
|
app(PermissionRegistrar::class)->forgetCachedPermissions();
|
|
|
|
$this->loadGrants();
|
|
$this->dispatch('notify', message: __('roles.deleted'));
|
|
}
|
|
|
|
private function role(int $roleId): Role
|
|
{
|
|
return Role::query()
|
|
->where('guard_name', self::GUARD)
|
|
->findOr($roleId, fn () => abort(404));
|
|
}
|
|
|
|
/** @return array<int, string> */
|
|
private function allPermissions(): array
|
|
{
|
|
return Permission::query()
|
|
->where('guard_name', self::GUARD)
|
|
->orderBy('name')
|
|
->pluck('name')
|
|
->all();
|
|
}
|
|
|
|
private function loadGrants(): void
|
|
{
|
|
$this->granted = Role::query()
|
|
->where('guard_name', self::GUARD)
|
|
->with('permissions')
|
|
->get()
|
|
->mapWithKeys(fn (Role $role) => [$role->id => $role->permissions->pluck('name')->all()])
|
|
->all();
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
return view('livewire.admin.roles', [
|
|
'roles' => Role::query()
|
|
->where('guard_name', self::GUARD)
|
|
->withCount('users')
|
|
->orderBy('name')
|
|
->get(),
|
|
'permissions' => $this->allPermissions(),
|
|
// Wie viele Menschen an dieser Rolle hängen — damit niemand blind
|
|
// etwas wegnimmt, das gerade sechs Leute benutzen.
|
|
'protectedRole' => 'Owner',
|
|
'ownRoles' => Operator::find(auth('operator')->id())?->roles->pluck('name')->all() ?? [],
|
|
]);
|
|
}
|
|
}
|