CluPilotCloud/app/Livewire/EditSeat.php

123 lines
3.9 KiB
PHP

<?php
namespace App\Livewire;
use App\Livewire\Concerns\ResolvesCustomer;
use App\Models\Seat;
use LivewireUI\Modal\ModalComponent;
/**
* Edit one seat, in a modal.
*
* Editing used to happen in the row itself. It worked and it looked wrong: the
* row grew, the columns beside it jumped, and a table half in edit mode reads
* as a rendering fault rather than as a form. Anything with fields of its own
* gets a modal — see R20.
*
* A modal is reachable without passing the page's route middleware, so the
* customer is resolved here rather than trusted from the caller: the uuid comes
* from the browser and must never reach across customers.
*/
class EditSeat extends ModalComponent
{
use ResolvesCustomer;
public string $uuid = '';
public string $name = '';
public string $email = '';
/** Owners cannot be suspended or removed; everyone can be renamed. */
public bool $isOwner = false;
/**
* Whether the address may still be corrected.
*
* Only until the seat has an account in the customer's cloud. From that
* point the address IS the person: editing it would hand one employee's
* access to another with nobody told — a transfer of access wearing the
* clothes of a rename.
*
* Die Bedingung hing an `status === 'invited'`, und das trug nicht: NICHTS
* im Bestand setzt `seats.status` je von 'invited' auf 'active'. Ein
* eingeladener Sitz blieb damit für immer 'invited' und seine Adresse
* dauerhaft änderbar — entgegen genau diesem Kommentar. `seats.email`
* wurde `b@y`, `nc_username` blieb `a@x` (richtig: Nextcloud kann Benutzer
* nicht umbenennen), und kein Befehl trug die neue Adresse in den Gast.
* „Erneut senden" schickte danach `user:welcome --reset-password 'a@x'`:
* das Portal führte `b@y` als Inhaberin des Sitzes, tatsächlich hielt
* `a@x` den Zugang und bekam jederzeit einen neuen Zurücksetzen-Link.
*
* `nc_username` ist die Angabe, die der Kommentar ohnehin meint — genau
* einmal gesetzt, danach unveränderlich.
*/
public bool $addressEditable = false;
public function mount(string $uuid): void
{
$seat = $this->seat($uuid);
abort_if($seat === null, 404);
$this->uuid = $uuid;
$this->name = (string) $seat->name;
$this->email = (string) $seat->email;
$this->isOwner = $seat->role === 'owner';
$this->addressEditable = blank($seat->nc_username);
}
public function save()
{
$customer = $this->requireCustomer();
$seat = $this->seat($this->uuid);
if ($customer === null || $seat === null) {
return $this->closeModal();
}
$rules = ['name' => 'nullable|string|max:255'];
// Re-read from the record, never from the hydrated property: a forged
// addressEditable would otherwise open the address of a seat that
// already has an account in the cloud.
if (blank($seat->nc_username)) {
$rules['email'] = 'required|email|max:255';
}
$data = $this->validate($rules);
$changes = ['name' => trim($data['name'] ?? '') ?: null];
if (blank($seat->nc_username)) {
$address = trim($data['email']);
if ($address !== $seat->email) {
if ($customer->seats()->where('email', $address)->whereKeyNot($seat->id)->exists()) {
$this->addError('email', __('users.duplicate'));
return null;
}
$changes['email'] = $address;
}
}
$seat->update($changes);
$this->dispatch('notify', message: __('users.saved'));
return $this->redirectRoute('users', navigate: true);
}
private function seat(string $uuid): ?Seat
{
return $this->customer()?->seats()->where('uuid', $uuid)->first();
}
public function render()
{
return view('livewire.edit-seat');
}
}