Elenco con solo numero e zona (territori attivi e quelli ancora fuori), mostrato solo per la riconsegna; il server accetta solo un numero dell'elenco. Privacy, README e HANDOFF aggiornati. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
138 lines
4.8 KiB
PHP
138 lines
4.8 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Richieste;
|
|
|
|
use App\Models\Richiesta;
|
|
use App\Models\Setting;
|
|
use App\Models\Territorio;
|
|
use App\Services\RichiesteServizio;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Support\Facades\RateLimiter;
|
|
use Illuminate\Support\Str;
|
|
use Livewire\Attributes\Locked;
|
|
use Livewire\Component;
|
|
|
|
/**
|
|
* Il modulo pubblico (/richiesta, senza login): un proclamatore chiede un
|
|
* territorio nuovo o annuncia una riconsegna. L'unico dato già presente che
|
|
* mostra è l'elenco dei territori nella riconsegna, per il completamento
|
|
* automatico del numero: numero e zona, mai a chi è assegnato né se lo è.
|
|
* Nomi e altri dati non si vedono.
|
|
*
|
|
* Contro lo spam, tre difese che a chi compila davvero non costano niente:
|
|
* - un campo trappola ($sito) nascosto alle persone e riempito dai programmi;
|
|
* - un tempo minimo fra apertura e invio (i programmi inviano subito);
|
|
* - al massimo MASSIMO_ORA richieste all'ora per indirizzo.
|
|
* Nei primi due casi si risponde come se fosse andata bene, senza salvare:
|
|
* chi prova non impara cosa l'ha fermato.
|
|
*/
|
|
class ModuloRichiesta extends Component
|
|
{
|
|
public const MASSIMO_ORA = 5;
|
|
|
|
public const SECONDI_MINIMI = 3;
|
|
|
|
public string $tipo = Richiesta::NUOVO;
|
|
|
|
public string $nome = '';
|
|
|
|
public string $numero = '';
|
|
|
|
public string $note = '';
|
|
|
|
/** Trappola: nascosto alle persone, i programmi lo riempiono. */
|
|
public string $sito = '';
|
|
|
|
#[Locked]
|
|
public int $apertoAlle = 0;
|
|
|
|
#[Locked]
|
|
public bool $inviata = false;
|
|
|
|
public function mount(): void
|
|
{
|
|
$this->apertoAlle = now()->timestamp;
|
|
}
|
|
|
|
public function invia(RichiesteServizio $richieste): void
|
|
{
|
|
if ($this->sito !== '' || now()->timestamp - $this->apertoAlle < self::SECONDI_MINIMI) {
|
|
$this->inviata = true;
|
|
|
|
return;
|
|
}
|
|
|
|
$chiave = 'richiesta-pubblica:' . sha1((string) request()->ip());
|
|
if (RateLimiter::tooManyAttempts($chiave, self::MASSIMO_ORA)) {
|
|
$this->addError('nome', "Hai già mandato diverse richieste: riprova fra un po'.");
|
|
|
|
return;
|
|
}
|
|
|
|
$this->validate([
|
|
'tipo' => 'required|in:' . Richiesta::NUOVO . ',' . Richiesta::RICONSEGNA,
|
|
'nome' => 'required|string|min:3|max:100',
|
|
'numero' => ['nullable', 'required_if:tipo,' . Richiesta::RICONSEGNA, 'string', 'max:20',
|
|
function ($attributo, $valore, $errore) {
|
|
if ($this->tipo === Richiesta::RICONSEGNA && ! self::territori()->whereRaw('LOWER(numero) = ?', [Str::lower(trim((string) $valore))])->exists()) {
|
|
$errore("Scegli il territorio dall'elenco.");
|
|
}
|
|
}],
|
|
'note' => 'nullable|string|max:1000',
|
|
], [
|
|
'nome.required' => 'Scrivi nome e cognome.',
|
|
'nome.min' => 'Scrivi nome e cognome.',
|
|
'numero.required_if' => 'Scegli il territorio che riconsegni.',
|
|
]);
|
|
|
|
RateLimiter::hit($chiave, 3600);
|
|
$richieste->registra($this->tipo, $this->nome, $this->numero, $this->note);
|
|
|
|
$this->reset(['nome', 'numero', 'note']);
|
|
$this->inviata = true;
|
|
}
|
|
|
|
public function nuova(): void
|
|
{
|
|
$this->reset(['tipo', 'nome', 'numero', 'note', 'inviata']);
|
|
$this->apertoAlle = now()->timestamp;
|
|
}
|
|
|
|
/**
|
|
* I territori che si possono riconsegnare: quelli attivi e quelli ancora
|
|
* fuori anche se nel frattempo disattivati.
|
|
*/
|
|
private static function territori(): Builder
|
|
{
|
|
return Territorio::query()->where(fn ($q) => $q->where('attivo', true)
|
|
->orWhereHas('assegnazioni', fn ($a) => $a->whereNull('returned_at')));
|
|
}
|
|
|
|
/**
|
|
* Le proposte del campo con ricerca (<x-scelta>): solo numero e zona. Il
|
|
* valore è il numero, come lo salva la richiesta.
|
|
*/
|
|
private static function opzioniTerritori(): array
|
|
{
|
|
return self::territori()->with('zona')->get()
|
|
->sortBy('numero', SORT_NATURAL)
|
|
->map(fn (Territorio $t) => array_filter([
|
|
'valore' => (string) $t->numero,
|
|
'etichetta' => "N° {$t->numero}",
|
|
'dettaglio' => $t->zona?->nome,
|
|
'chiavi' => [(string) $t->numero, (string) $t->zona?->nome],
|
|
], fn ($v) => $v !== null))
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
return view('livewire.richieste.modulo-richiesta', [
|
|
'congregazione' => Setting::instance()->congregazione_nome ?: 'TerManager2',
|
|
// Solo quando serve: chi chiede un territorio nuovo non vede l'elenco.
|
|
'opzioniTerritori' => $this->tipo === Richiesta::RICONSEGNA && ! $this->inviata ? self::opzioniTerritori() : [],
|
|
])->layout('components.layouts.guest', ['title' => 'Richiesta territorio']);
|
|
}
|
|
}
|