123 lines
3.9 KiB
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');
|
|
}
|
|
}
|