126 lines
4.0 KiB
PHP
126 lines
4.0 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Admin;
|
|
|
|
use App\Models\Mailbox;
|
|
use App\Services\Mail\MailboxTester;
|
|
use App\Services\Mail\MailPurpose;
|
|
use App\Services\Secrets\SecretCipher;
|
|
use App\Support\Settings;
|
|
use Livewire\Attributes\Layout;
|
|
use Livewire\Component;
|
|
|
|
/**
|
|
* The sending addresses, and which kind of mail leaves from which.
|
|
*
|
|
* The server sits at the top because there is one of it; the mailboxes are a
|
|
* list because there are several; the mapping is last because it only makes
|
|
* sense once both exist. The test-send button lives with the mailboxes: it
|
|
* proves one specific mailbox can actually send, which is the only thing
|
|
* that makes the rest of this page more than a form.
|
|
*/
|
|
#[Layout('layouts.admin')]
|
|
class Mail extends Component
|
|
{
|
|
public string $host = '';
|
|
|
|
public int|string $port = 587;
|
|
|
|
public string $encryption = 'tls';
|
|
|
|
/** @var array<string, string> purpose => mailbox key */
|
|
public array $purposes = [];
|
|
|
|
public string $testRecipient = '';
|
|
|
|
/** @var array{ok: bool, error: ?string}|null */
|
|
public ?array $testResult = null;
|
|
|
|
public ?string $testedKey = null;
|
|
|
|
/**
|
|
* Whether credentials can be stored at all on this installation.
|
|
*
|
|
* Public so the page can say it rather than throwing on the first password
|
|
* a mailbox tries to decrypt — the same courtesy the secrets page already
|
|
* extends. A missing SECRETS_KEY is a setup state, not an error.
|
|
*/
|
|
public bool $usable = true;
|
|
|
|
public function mount(): void
|
|
{
|
|
$this->authorize('mail.manage');
|
|
|
|
$this->host = (string) Settings::get('mail.host', '');
|
|
$this->port = (int) Settings::get('mail.port', 587);
|
|
$this->encryption = (string) Settings::get('mail.encryption', 'tls');
|
|
|
|
foreach (MailPurpose::ALL as $purpose) {
|
|
$this->purposes[$purpose] = (string) Settings::get(MailPurpose::settingKey($purpose), '');
|
|
}
|
|
}
|
|
|
|
public function saveServer(): void
|
|
{
|
|
$this->authorize('mail.manage');
|
|
|
|
// Rules on the ACTION, not on the property: a #[Validate] attribute on
|
|
// a Livewire property applies class-wide, and savePurposes() below
|
|
// would drag these along.
|
|
$this->validate([
|
|
'host' => ['required', 'string', 'max:255'],
|
|
'port' => ['required', 'integer', 'min:1', 'max:65535'],
|
|
'encryption' => ['required', 'in:tls,ssl,none'],
|
|
]);
|
|
|
|
Settings::set('mail.host', $this->host);
|
|
Settings::set('mail.port', (int) $this->port);
|
|
Settings::set('mail.encryption', $this->encryption);
|
|
|
|
$this->dispatch('notify', message: __('mail_settings.server_saved'));
|
|
}
|
|
|
|
public function savePurposes(): void
|
|
{
|
|
$this->authorize('mail.manage');
|
|
|
|
// system is the fallback, so it is the one that may not be empty —
|
|
// there is nothing left to fall back to.
|
|
$this->validate(
|
|
['purposes.system' => ['required', 'string']],
|
|
['purposes.system.required' => __('mail_settings.system_required')],
|
|
);
|
|
|
|
foreach (MailPurpose::ALL as $purpose) {
|
|
Settings::set(MailPurpose::settingKey($purpose), $this->purposes[$purpose] ?? '');
|
|
}
|
|
|
|
$this->dispatch('notify', message: __('mail_settings.purposes_saved'));
|
|
}
|
|
|
|
public function test(string $uuid): void
|
|
{
|
|
$this->authorize('mail.manage');
|
|
|
|
$this->validate(
|
|
['testRecipient' => ['required', 'email']],
|
|
['testRecipient.required' => __('mail_settings.test_recipient_required')],
|
|
);
|
|
|
|
$box = Mailbox::query()->where('uuid', $uuid)->firstOrFail();
|
|
|
|
$this->testedKey = $box->key;
|
|
$this->testResult = app(MailboxTester::class)->run($box, $this->testRecipient);
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
$this->usable = app(SecretCipher::class)->isUsable();
|
|
|
|
return view('livewire.admin.mail', [
|
|
'mailboxes' => Mailbox::query()->orderBy('key')->get(),
|
|
'purposeList' => MailPurpose::ALL,
|
|
]);
|
|
}
|
|
}
|