null, 'name' => '', 'enabled' => true, 'host' => '', 'port' => 993, 'encryption' => 'ssl', 'validateCert' => true, 'username' => '', 'password' => '', 'folder' => 'INBOX', 'processedFolder' => '', 'rejectedFolder' => '', 'target' => '', 'blocklistSenders' => 'mailer-daemon,postmaster,no-reply,noreply', ]; public ?int $mailboxTestResultId = null; public ?string $mailboxTestResult = null; public ?string $mailboxTestMessage = null; public ?int $mailboxFetchResultId = null; public ?string $mailboxFetchSummary = null; public function mount(): void { $this->mailConfig = [ 'smtpEnabled' => Settings::bool('mail_smtp_enabled'), 'smtpHost' => Settings::get('mail_smtp_host'), 'smtpPort' => Settings::get('mail_smtp_port'), 'smtpUsername' => Settings::get('mail_smtp_username'), 'smtpPassword' => Settings::get('mail_smtp_password'), 'smtpEncryption' => Settings::get('mail_smtp_encryption'), 'fromAddress' => Settings::get('mail_from_address'), 'fromName' => Settings::get('mail_from_name'), ]; } #[Computed] public function mailboxes(): Collection { return ImapMailbox::query()->with(['defaultSubcategory.category', 'defaultCategory'])->orderBy('name')->get(); } /** * Categories with their subcategories nested, for the mailbox form's * single combined "cała kategoria albo konkretna podkategoria" selector. */ #[Computed] public function categoryOptions(): Collection { return Category::query()->with('subcategories')->orderBy('name')->get() ->map(fn (Category $c) => [ 'id' => $c->id, 'name' => $c->name, 'subcategories' => $c->subcategories->map(fn ($s) => ['id' => $s->id, 'name' => $s->name])->values(), ]) ->values(); } // ===================== SMTP ===================== public function saveMailConfig(): void { Settings::set('mail_smtp_enabled', $this->mailConfig['smtpEnabled'] ? '1' : '0'); Settings::set('mail_smtp_host', $this->mailConfig['smtpHost']); Settings::set('mail_smtp_port', (string) $this->mailConfig['smtpPort']); Settings::set('mail_smtp_username', $this->mailConfig['smtpUsername']); if ($this->mailConfig['smtpPassword']) { Settings::set('mail_smtp_password', $this->mailConfig['smtpPassword']); } Settings::set('mail_smtp_encryption', $this->mailConfig['smtpEncryption']); Settings::set('mail_from_address', $this->mailConfig['fromAddress']); Settings::set('mail_from_name', $this->mailConfig['fromName']); $this->mailTestResult = null; } /** * Sends a real test e-mail to the logged-in admin using the form's * current (unsaved) values, temporarily overriding the mail config the * same way AppServiceProvider does for real once saved. */ public function testMailConnection(): void { $cfg = $this->mailConfig; if (empty($cfg['smtpHost']) || empty($cfg['fromAddress'])) { $this->mailTestResult = 'error'; return; } $original = Config::get('mail'); try { Config::set('mail.default', 'smtp'); Config::set('mail.mailers.smtp.host', $cfg['smtpHost']); Config::set('mail.mailers.smtp.port', (int) $cfg['smtpPort']); Config::set('mail.mailers.smtp.username', $cfg['smtpUsername'] ?: null); Config::set('mail.mailers.smtp.password', $cfg['smtpPassword'] ?: Settings::get('mail_smtp_password')); Config::set('mail.mailers.smtp.scheme', match ($cfg['smtpEncryption']) { 'ssl' => 'smtps', 'tls' => 'smtp', default => null, }); Config::set('mail.from.address', $cfg['fromAddress']); Config::set('mail.from.name', $cfg['fromName'] ?: Settings::get('company_name')); app()->forgetInstance('mail.manager'); app()->forgetInstance('mailer'); Mail::raw('To jest testowa wiadomość wysłana z panelu administratora Servicedesk.', function ($message) { $message->to(Auth::user()->email)->subject('Test konfiguracji SMTP'); }); $this->mailTestResult = 'ok'; } catch (\Throwable) { $this->mailTestResult = 'error'; } finally { Config::set('mail', $original); app()->forgetInstance('mail.manager'); app()->forgetInstance('mailer'); } } // ===================== IMAP MAILBOXES ===================== public function openMailboxForm(): void { $this->reset('mailboxForm'); $this->mailboxForm = [ 'id' => null, 'name' => '', 'enabled' => true, 'host' => '', 'port' => 993, 'encryption' => 'ssl', 'validateCert' => true, 'username' => '', 'password' => '', 'folder' => 'INBOX', 'processedFolder' => '', 'rejectedFolder' => '', 'target' => '', 'blocklistSenders' => 'mailer-daemon,postmaster,no-reply,noreply', ]; $this->mailboxTestResultId = null; $this->resetErrorBag(); $this->mailboxFormOpen = true; } public function editMailbox(int $id): void { $mailbox = ImapMailbox::query()->findOrFail($id); $target = match (true) { (bool) $mailbox->default_subcategory_id => "subcategory:{$mailbox->default_subcategory_id}", (bool) $mailbox->default_category_id => "category:{$mailbox->default_category_id}", default => '', }; $this->mailboxForm = [ 'id' => $mailbox->id, 'name' => $mailbox->name, 'enabled' => $mailbox->enabled, 'host' => $mailbox->host, 'port' => $mailbox->port, 'encryption' => $mailbox->encryption, 'validateCert' => $mailbox->validate_cert, 'username' => $mailbox->username, 'password' => $mailbox->password, 'folder' => $mailbox->folder, 'processedFolder' => $mailbox->processed_folder, 'rejectedFolder' => $mailbox->rejected_folder, 'target' => $target, 'blocklistSenders' => $mailbox->blocklist_senders, ]; $this->mailboxTestResultId = null; $this->resetErrorBag(); $this->mailboxFormOpen = true; } public function closeMailboxForm(): void { $this->mailboxFormOpen = false; } public function submitMailboxForm(): void { $this->validate([ 'mailboxForm.name' => ['required', 'string', 'max:255'], 'mailboxForm.host' => ['required', 'string', 'max:255'], 'mailboxForm.port' => ['required', 'integer', 'min:1', 'max:65535'], 'mailboxForm.encryption' => ['required', 'in:ssl,tls,none'], 'mailboxForm.username' => ['required', 'string', 'max:255'], 'mailboxForm.folder' => ['required', 'string', 'max:255'], ]); [$targetType, $targetId] = str_contains((string) $this->mailboxForm['target'], ':') ? explode(':', $this->mailboxForm['target'], 2) : [null, null]; $data = [ 'name' => $this->mailboxForm['name'], 'enabled' => (bool) $this->mailboxForm['enabled'], 'host' => $this->mailboxForm['host'], 'port' => (int) $this->mailboxForm['port'], 'encryption' => $this->mailboxForm['encryption'], 'validate_cert' => (bool) $this->mailboxForm['validateCert'], 'username' => $this->mailboxForm['username'], 'folder' => $this->mailboxForm['folder'], 'processed_folder' => $this->mailboxForm['processedFolder'] ?: null, 'rejected_folder' => $this->mailboxForm['rejectedFolder'] ?: null, // Exactly one of these (or neither) — never both — driven by the // form's single "cała kategoria albo konkretna podkategoria" selector. 'default_subcategory_id' => $targetType === 'subcategory' ? $targetId : null, 'default_category_id' => $targetType === 'category' ? $targetId : null, 'blocklist_senders' => $this->mailboxForm['blocklistSenders'], ]; $mailbox = ImapMailbox::query()->find($this->mailboxForm['id']); if ($mailbox) { if ($this->mailboxForm['password']) { $data['password'] = $this->mailboxForm['password']; } $mailbox->update($data); } else { $data['password'] = $this->mailboxForm['password']; ImapMailbox::query()->create($data); } $this->mailboxFormOpen = false; unset($this->mailboxes); } public function toggleMailboxEnabled(int $id): void { $mailbox = ImapMailbox::query()->findOrFail($id); $mailbox->update(['enabled' => ! $mailbox->enabled]); unset($this->mailboxes); } public function removeMailbox(int $id): void { ImapMailbox::query()->findOrFail($id)->delete(); unset($this->mailboxes); } /** * Runs a real fetch against one mailbox right now, outside the 5-minute * schedule — for checking a freshly-configured mailbox without waiting, * and for diagnosing "why didn't my e-mail turn into a ticket" without * needing shell access. Allowed even while the mailbox is disabled * (fetchAll(), used by the scheduled command, is the one that respects * the enabled flag — this is an explicit admin action). */ public function fetchMailboxNow(int $id): void { $mailbox = ImapMailbox::query()->findOrFail($id); $result = app(ImapMailboxFetcher::class)->fetchMailbox($mailbox); $this->mailboxFetchResultId = $id; $this->mailboxFetchSummary = "Nowe: {$result['created']}, odpowiedzi: {$result['replied']}, odrzucone: {$result['rejected']}, błędy: {$result['errors']}."; unset($this->mailboxes); } /** * Tests the form's current (unsaved) values against a throwaway * ImapMailbox instance — mirrors testMailConnection()'s "don't require a * save first" behavior. Falls back to the stored password when editing * an existing mailbox and the password field was left blank. */ public function testMailboxConnection(): void { $mailbox = new ImapMailbox([ 'host' => $this->mailboxForm['host'], 'port' => (int) $this->mailboxForm['port'], 'encryption' => $this->mailboxForm['encryption'], 'validate_cert' => (bool) $this->mailboxForm['validateCert'], 'username' => $this->mailboxForm['username'], 'folder' => $this->mailboxForm['folder'] ?: 'INBOX', ]); $mailbox->password = $this->mailboxForm['password'] ?: ($this->mailboxForm['id'] ? ImapMailbox::query()->find($this->mailboxForm['id'])?->password : null); $error = app(ImapMailboxFetcher::class)->testConnection($mailbox); $this->mailboxTestResultId = (int) ($this->mailboxForm['id'] ?? 0); $this->mailboxTestResult = $error === null ? 'ok' : 'error'; $this->mailboxTestMessage = $error; } public function render() { return view('livewire.admin.mail-settings'); } }