diff --git a/app/Helpers/helpers.php b/app/Helpers/helpers.php index 405f676..6e423ae 100644 --- a/app/Helpers/helpers.php +++ b/app/Helpers/helpers.php @@ -53,3 +53,18 @@ if (!function_exists('mta_host')) { return domain_host('mx'); } } + +if (! function_exists('countryFlag')) { + /** + * Gibt das Flag-Emoji eines Landes zurück anhand des ISO-Codes (z. B. "de" → 🇩🇪). + */ + function countryFlag(string $code): string + { + $code = strtoupper($code); + // Unicode-Magic: A -> 🇦, B -> 🇧 etc. + return implode('', array_map( + fn($char) => mb_chr(ord($char) + 127397, 'UTF-8'), + str_split($code) + )); + } +} diff --git a/app/Livewire/Ui/Mail/Modal/AliasFormModal.php b/app/Livewire/Ui/Mail/Modal/AliasFormModal.php index 94c7192..b40017f 100644 --- a/app/Livewire/Ui/Mail/Modal/AliasFormModal.php +++ b/app/Livewire/Ui/Mail/Modal/AliasFormModal.php @@ -439,1761 +439,7 @@ class AliasFormModal extends ModalComponent public function render() { return view('livewire.ui.mail.modal.alias-form-modal', [ - 'domains' => Domain::where('is_system', false)->orderBy('domain')->get(['id', 'domain']), + 'domains' => Domain::where('is_system', false)->where('is_server', false)->orderBy('domain')->get(['id', 'domain']), ]); } } - -//namespace App\Livewire\Ui\Mail\Modal; -// -//use App\Models\Domain; -//use App\Models\MailAlias; -//use App\Models\MailUser; -//use Illuminate\Support\Facades\DB; -//use Illuminate\Support\Str; -//use Illuminate\Validation\Rule; -//use LivewireUI\Modal\ModalComponent; -// -//class AliasFormModal extends ModalComponent -//{ -// // Eingabe / State -// public ?int $aliasId = null; -// public ?int $domainId = null; -// public string $local = ''; -// public string $type = 'single'; // single|group -// public bool $is_active = true; -// public ?string $notes = null; -// -// // Empfänger-Zeilen (each: ['mail_user_id'=>?int, 'email'=>?string]) -// public array $recipients = []; -// -// // Stabile Keys je Zeile (für wire:key im Blade) -// public array $rowKeys = []; -// -// // UI-Steuerung (nur Ausgabe im Blade) -// public int $maxGroupRecipients = 20; -// public bool $canAddRecipient = false; // „+ Empfänger“ erlauben -// public array $rowState = []; // [i => ['disable_internal'=>bool,'disable_external'=>bool,'can_remove'=>bool]] -// public array $disabledMailboxIdsByRow = []; // [i => [ids...]] -// -// // Lookup -// public ?Domain $domain = null; -// /** @var \Illuminate\Support\Collection */ -// public $domainMailUsers; -// -// // -------------------- Lifecycle -------------------- -// -// public function mount(?int $aliasId = null, ?int $domainId = null): void -// { -// $this->aliasId = $aliasId; -// $this->domainId = $domainId; -// -// if ($this->aliasId) { -// $alias = MailAlias::with(['domain', 'recipients.mailUser'])->findOrFail($this->aliasId); -// if ($alias->domain->is_system) { -// abort(403, 'System-Domains sind für Aliasse gesperrt.'); -// } -// $this->domain = $alias->domain; -// $this->domainId = $alias->domain_id; -// -// $this->fill([ -// 'local' => $alias->local, -// 'type' => $alias->type, -// 'is_active' => $alias->is_active, -// 'notes' => $alias->notes, -// ]); -// -// $this->recipients = $alias->recipients -// ->map(fn($r) => ['mail_user_id' => $r->mail_user_id, 'email' => $r->email]) -// ->all(); -// } else { -// // erste Nicht-System-Domain -// $this->domain = $this->domainId -// ? Domain::where('is_system', false)->findOrFail($this->domainId) -// : Domain::where('is_system', false)->orderBy('domain')->first(); -// -// $this->domainId = $this->domain->id ?? null; -// $this->recipients = [['mail_user_id' => null, 'email' => null]]; -// } -// -// $this->domainMailUsers = $this->domain -// ? MailUser::where('domain_id', $this->domain->id)->orderBy('localpart')->get() -// : collect(); -// -// $this->ensureAtLeastOneRow(); -// $this->syncRowKeysToRecipients(); -// -// $this->recomputeUi(); -// $this->validateRecipientDuplicates(); // sofortige Feldfehler bei vorbefüllten Daten -// } -// -// // -------------------- UI-Reaktionen -------------------- -// -// public function updated($name, $value): void -// { -// // XOR: wenn intern gesetzt -> extern leeren, und umgekehrt -// if (preg_match('/^recipients\.(\d+)\.(mail_user_id|email)$/', $name, $m)) { -// $i = (int)$m[1]; -// if ($m[2] === 'mail_user_id' && !empty($value)) { -// $this->recipients[$i]['email'] = null; -// } elseif ($m[2] === 'email') { -// $this->recipients[$i]['mail_user_id'] = null; -// $this->recipients[$i]['email'] = Str::lower(trim((string)$this->recipients[$i]['email'])); -// } -// } -// -// // Self-Loop Live-Feedback: extern == alias -// if (preg_match('/^recipients\.(\d+)\.email$/', $name, $m)) { -// $i = (int)$m[1]; -// $self = $this->currentAliasAddress(); -// $this->resetErrorBag("recipients.$i.email"); -// if ($self && Str::lower(trim((string)$this->recipients[$i]['email'])) === $self) { -// $this->addError("recipients.$i.email", 'Alias darf nicht an sich selbst weiterleiten.'); -// } -// } -// -// if ($name === 'type' && $this->type === 'single') { -// $this->recipients = [$this->recipients[0] ?? ['mail_user_id' => null, 'email' => null]]; -// } -// -// $this->ensureAtLeastOneRow(); -// $this->syncRowKeysToRecipients(); -// $this->recomputeUi(); -// $this->validateRecipientDuplicates(); // feldgenaues Feedback live -// } -// -// public function updatedType(string $value): void -// { -// if ($value === 'single') { -// $first = $this->recipients[0] ?? ['mail_user_id' => null, 'email' => null]; -// $this->recipients = [$first]; -// $this->syncRowKeysToRecipients(); -// $this->recomputeUi(); -// $this->validateRecipientDuplicates(); -// } -// } -// -// public function updatedDomainId(): void -// { -// $this->domain = $this->domainId -// ? Domain::where('is_system', false)->find($this->domainId) -// : null; -// -// $this->domainMailUsers = $this->domain -// ? MailUser::where('domain_id', $this->domain->id)->orderBy('localpart')->get() -// : collect(); -// -// // Zeilen resetten, weil Empfänger domaingebunden sind -// $this->recipients = [['mail_user_id' => null, 'email' => null]]; -// -// $this->ensureAtLeastOneRow(); -// $this->syncRowKeysToRecipients(); -// $this->recomputeUi(); -// $this->validateRecipientDuplicates(); -// } -// -// public function addRecipientRow(): void -// { -// if ($this->type === 'single') return; -// if (count($this->recipients) >= $this->maxGroupRecipients) return; -// -// $this->recipients[] = ['mail_user_id' => null, 'email' => null]; -// $this->rowKeys[] = (string)Str::uuid(); // stabiler Key für neue Zeile -// -// $this->recomputeUi(); -// // kein Duplicate hier, weil leer -// } -// -// public function removeRecipientRow(int $index): void -// { -// unset($this->recipients[$index], $this->rowKeys[$index]); -// $this->recipients = array_values($this->recipients); -// $this->rowKeys = array_values($this->rowKeys); -// -// $this->ensureAtLeastOneRow(); -// $this->syncRowKeysToRecipients(); -// $this->recomputeUi(); -// $this->validateRecipientDuplicates(); -// } -// -// // -------------------- Helpers -------------------- -// -// // vollständige Alias-Adresse (z.B. "info@pixio.at") oder null -// private function currentAliasAddress(): ?string -// { -// if (!$this->domainId || $this->local === '') return null; -// -// $domainName = $this->domain?->domain ?? Domain::find($this->domainId)?->domain; -// if (!$domainName) return null; -// -// return Str::lower(trim($this->local)) . '@' . Str::lower($domainName); -// } -// -// private function ensureAtLeastOneRow(): void -// { -// if (empty($this->recipients)) { -// $this->recipients = [['mail_user_id' => null, 'email' => null]]; -// } -// } -// -// // rowKeys-Liste immer an recipients-Länge anpassen (stabile wire:key Werte) -// private function syncRowKeysToRecipients(): void -// { -// $need = count($this->recipients); -// $have = count($this->rowKeys); -// -// // auffüllen -// while ($have < $need) { -// $this->rowKeys[] = (string)Str::uuid(); -// $have++; -// } -// // kürzen -// if ($have > $need) { -// $this->rowKeys = array_slice($this->rowKeys, 0, $need); -// } -// } -// -// // UI neu berechnen (inkl. Self-Loop-Schutz) -// private function recomputeUi(): void -// { -// $count = count($this->recipients); -// $this->canAddRecipient = $this->type === 'group' && $count < $this->maxGroupRecipients; -// -// // bereits gewählte interne IDs und externe Adressen einsammeln -// $usedMailboxIds = []; -// $usedExternalAddr = []; -// -// foreach ($this->recipients as $r) { -// if (!empty($r['mail_user_id'])) $usedMailboxIds[] = (int)$r['mail_user_id']; -// if (!empty($r['email'])) $usedExternalAddr[] = Str::lower(trim((string)$r['email'])); -// } -// -// $this->disabledMailboxIdsByRow = []; -// $this->rowState = []; -// -// // Aktuelle Alias-Adresse für Self-Loop-Checks -// $aliasAddr = $this->currentAliasAddress(); -// -// for ($i = 0; $i < $count; $i++) { -// $myId = (int)($this->recipients[$i]['mail_user_id'] ?? 0); -// $hasInternal = $myId > 0; -// $hasExternal = !empty($this->recipients[$i]['email']); -// $myExternal = Str::lower(trim((string)($this->recipients[$i]['email'] ?? ''))); -// -// // andere bereits gewählte interne IDs sperren -// $disabled = array_values(array_unique(array_filter( -// $usedMailboxIds, fn($id) => $id && $id !== $myId -// ))); -// -// // interne IDs zusätzlich sperren, deren Adresse in irgendeinem externen Feld steht -// if ($this->domain) { -// foreach ($this->domainMailUsers as $mu) { -// $addr = Str::lower($mu->localpart . '@' . $this->domain->domain); -// if (in_array($addr, $usedExternalAddr, true) && $mu->id !== $myId) { -// $disabled[] = (int)$mu->id; -// } -// } -// } -// -// $this->disabledMailboxIdsByRow[$i] = array_values(array_unique($disabled)); -// -// // Self-Loop: wenn extern == alias, internen Picker sperren -// $disableInternal = $hasExternal || ($aliasAddr && $aliasAddr === $myExternal); -// -// $this->rowState[$i] = [ -// 'disable_internal' => $disableInternal, // XOR + Self-Loop -// 'disable_external' => $hasInternal, // XOR -// 'can_remove' => !($this->type === 'single' && $count === 1), -// ]; -// } -// } -// -// // Duplikate feldgenau markieren (keine Exceptions) -// private function validateRecipientDuplicates(): bool -// { -// // komplette Empfänger-Fehler zurücksetzen -// $this->resetErrorBag(); -// -// // Map Zeile -> normalisierte Zieladresse (intern => local@domain, extern => email) -// $values = []; // [i => 'addr'] -// $byAddrIndices = []; // 'addr' => [i1,i2,...] -// -// foreach ($this->recipients as $i => $r) { -// $addr = null; -// -// if (!empty($r['mail_user_id'])) { -// if ($this->domain) { -// $u = MailUser::find((int)$r['mail_user_id']); -// if ($u) $addr = Str::lower($u->localpart . '@' . $this->domain->domain); -// } -// } elseif (!empty($r['email'])) { -// $addr = Str::lower(trim((string)$r['email'])); -// } -// -// if ($addr) { -// $values[$i] = $addr; -// $byAddrIndices[$addr] = $byAddrIndices[$addr] ?? []; -// $byAddrIndices[$addr][] = $i; -// } -// } -// -// $hasDupes = false; -// -// foreach ($byAddrIndices as $addr => $indices) { -// if (count($indices) <= 1) continue; -// $hasDupes = true; -// // alle betroffenen Felder markieren (feldgenau) -// foreach ($indices as $i) { -// if (!empty($this->recipients[$i]['mail_user_id'])) { -// $this->addError("recipients.$i.mail_user_id", 'Dieser Empfänger ist bereits hinzugefügt.'); -// } else { -// $this->addError("recipients.$i.email", 'Dieser Empfänger ist bereits hinzugefügt.'); -// } -// } -// } -// -// return $hasDupes; -// } -// -// private function aliasConflictsWithMailbox(): bool -// { -// if (!$this->domainId || $this->local === '') return false; -// -// $local = Str::lower(trim($this->local)); -// return MailUser::query() -// ->where('domain_id', $this->domainId) -// ->whereRaw('LOWER(localpart) = ?', [$local]) -// ->exists(); -// } -// -// // -------------------- Save -------------------- -// -// public function save(): void -// { -// // 1) Basisregeln -// $this->validate($this->rules(), $this->messages()); -// -// // 2) Zeilen dürfen nicht leer sein (weder intern noch extern) -// foreach ($this->recipients as $i => $r) { -// if (empty($r['mail_user_id']) && empty($r['email'])) { -// $this->addError("recipients.$i.email", 'Wähle internes Postfach oder externe E-Mail.'); -// return; -// } -// } -// -// // 3) Duplikate feldgenau prüfen -// if ($this->validateRecipientDuplicates()) { -// return; -// } -// -// // 3b) Self-Loop final abfangen -// if ($aliasAddr = $this->currentAliasAddress()) { -// foreach ($this->recipients as $i => $r) { -// // extern -// if (!empty($r['email']) && Str::lower(trim((string)$r['email'])) === $aliasAddr) { -// $this->addError("recipients.$i.email", 'Alias darf nicht an sich selbst weiterleiten.'); -// return; -// } -// // intern (falls je relevant) -// if (!empty($r['mail_user_id'])) { -// $u = MailUser::find((int)$r['mail_user_id']); -// if ($u && $this->domain && Str::lower($u->localpart . '@' . $this->domain->domain) === $aliasAddr) { -// $this->addError("recipients.$i.mail_user_id", 'Alias darf nicht an sich selbst weiterleiten.'); -// return; -// } -// } -// } -// } -// -// // 4) Alias-Adresse darf kein bestehendes Postfach sein -// if ($this->aliasConflictsWithMailbox()) { -// $this->addError('local', 'Diese Adresse ist bereits als Postfach vergeben.'); -// return; -// } -// -// // 5) Persistieren -// $rows = collect($this->recipients) -// ->map(fn($r) => [ -// 'mail_user_id' => $r['mail_user_id'] ?: null, -// 'email' => isset($r['email']) && $r['email'] !== '' ? Str::lower(trim($r['email'])) : null, -// ]) -// ->filter(fn($r) => $r['mail_user_id'] || $r['email']) -// ->values(); -// -// DB::transaction(function () use ($rows) { -// /** @var MailAlias $alias */ -// $alias = MailAlias::updateOrCreate( -// ['id' => $this->aliasId], -// [ -// 'domain_id' => $this->domainId, -// 'local' => $this->local, -// 'type' => $this->type, -// 'is_active' => $this->is_active, -// 'notes' => $this->notes, -// ] -// ); -// -// $alias->recipients()->delete(); -// foreach ($rows as $i => $r) { -// $alias->recipients()->create([ -// 'mail_user_id' => $r['mail_user_id'], -// 'email' => $r['email'], -// 'position' => $i, -// ]); -// } -// -// $this->aliasId = $alias->id; -// }); -// -// $this->dispatch('alias:' . ($this->aliasId ? 'updated' : 'created')); -// $this->dispatch('closeModal'); -// } -// -// // -------------------- Validation -------------------- -// -// protected function rules(): array -// { -// // blocke offensichtliche Kollisionen mit Mailboxen (gleiche Domain) -// $occupied = $this->domainId -// ? MailUser::query() -// ->where('domain_id', $this->domainId) -// ->pluck('localpart') -// ->map(fn($l) => Str::lower($l)) -// ->all() -// : []; -// -// return [ -// 'domainId' => ['required', 'exists:domains,id'], -// 'local' => [ -// 'required', -// 'regex:/^[A-Za-z0-9._%+-]+$/', -// Rule::unique('mail_aliases', 'local') -// ->ignore($this->aliasId) -// ->where(fn($q) => $q->where('domain_id', $this->domainId)), -// Rule::notIn($occupied), -// ], -// 'type' => ['required', 'in:single,group'], -// -// 'recipients' => ['array', 'min:1', 'max:' . ($this->type === 'single' ? 1 : $this->maxGroupRecipients)], -// 'recipients.*.mail_user_id' => ['nullable', 'exists:mail_users,id'], -// 'recipients.*.email' => ['nullable', 'email:rfc'], -// ]; -// } -// -// protected function messages(): array -// { -// return [ -// 'local.required' => 'Alias-Adresse ist erforderlich.', -// 'local.regex' => 'Erlaubt sind Buchstaben, Zahlen und ._%+-', -// 'local.unique' => 'Dieser Alias existiert in dieser Domain bereits.', -// 'local.not_in' => 'Diese Adresse ist bereits als Postfach vergeben.', -// 'recipients.min' => 'Mindestens ein Empfänger ist erforderlich.', -// 'recipients.max' => $this->type === 'single' -// ? 'Bei „Single“ ist nur ein Empfänger zulässig.' -// : 'Maximal ' . $this->maxGroupRecipients . ' Empfänger erlaubt.', -// ]; -// } -// -// public function render() -// { -// return view('livewire.ui.mail.modal.alias-form-modal', [ -// 'domains' => Domain::where('is_system', false) -// ->orderBy('domain') -// ->get(['id', 'domain']), -// ]); -// } -//} - - -//namespace App\Livewire\Ui\Mail\Modal; -// -//use App\Models\Domain; -//use App\Models\MailAlias; -//use App\Models\MailUser; -//use Illuminate\Support\Facades\DB; -//use Illuminate\Support\Str; -//use Illuminate\Validation\Rule; -//use LivewireUI\Modal\ModalComponent; -// -//class AliasFormModal extends ModalComponent -//{ -// // Eingabe / State -// public ?int $aliasId = null; -// public ?int $domainId = null; -// public string $local = ''; -// public string $type = 'single'; // single|group -// public bool $is_active = true; -// public ?string $notes = null; -// -// // Empfänger-Zeilen (each: ['mail_user_id'=>?int, 'email'=>?string]) -// public array $recipients = []; -// -// // UI-Steuerung (nur Ausgabe im Blade) -// public int $maxGroupRecipients = 20; -// public bool $canAddRecipient = false; // „+ Empfänger“ erlauben -// public array $rowState = []; // [i => ['disable_internal'=>bool,'disable_external'=>bool,'can_remove'=>bool]] -// public array $disabledMailboxIdsByRow = []; // [i => [ids...]] -// -// // Lookup -// public ?Domain $domain = null; -// /** @var \Illuminate\Support\Collection */ -// public $domainMailUsers; -// -// // -------------------- Lifecycle -------------------- -// -// public function mount(?int $aliasId = null, ?int $domainId = null): void -// { -// $this->aliasId = $aliasId; -// $this->domainId = $domainId; -// -// if ($this->aliasId) { -// $alias = MailAlias::with(['domain', 'recipients.mailUser'])->findOrFail($this->aliasId); -// if ($alias->domain->is_system) { -// abort(403, 'System-Domains sind für Aliasse gesperrt.'); -// } -// $this->domain = $alias->domain; -// $this->domainId = $alias->domain_id; -// -// $this->fill([ -// 'local' => $alias->local, -// 'type' => $alias->type, -// 'is_active' => $alias->is_active, -// 'notes' => $alias->notes, -// ]); -// -// $this->recipients = $alias->recipients -// ->map(fn($r) => ['mail_user_id' => $r->mail_user_id, 'email' => $r->email]) -// ->all(); -// } else { -// // erste Nicht-System-Domain -// $this->domain = $this->domainId -// ? Domain::where('is_system', false)->findOrFail($this->domainId) -// : Domain::where('is_system', false)->orderBy('domain')->first(); -// -// $this->domainId = $this->domain->id ?? null; -// $this->recipients = [['mail_user_id' => null, 'email' => null]]; -// } -// -// $this->domainMailUsers = $this->domain -// ? MailUser::where('domain_id', $this->domain->id)->orderBy('localpart')->get() -// : collect(); -// -// $this->ensureAtLeastOneRow(); -// $this->recomputeUi(); -// $this->validateRecipientDuplicates(); // sofortige Feldfehler bei vorbefüllten Daten -// } -// -// // -------------------- UI-Reaktionen -------------------- -// -// public function updated($name, $value): void -// { -// // XOR: wenn intern gesetzt -> extern leeren, und umgekehrt -// if (preg_match('/^recipients\.(\d+)\.(mail_user_id|email)$/', $name, $m)) { -// $i = (int)$m[1]; -// if ($m[2] === 'mail_user_id' && !empty($value)) { -// $this->recipients[$i]['email'] = null; -// } elseif ($m[2] === 'email') { -// $this->recipients[$i]['mail_user_id'] = null; -// $this->recipients[$i]['email'] = Str::lower(trim((string)$this->recipients[$i]['email'])); -// } -// } -// -// // Self-Loop Live-Feedback: extern == alias -// if (preg_match('/^recipients\.(\d+)\.email$/', $name, $m)) { -// $i = (int)$m[1]; -// $self = $this->currentAliasAddress(); -// $this->resetErrorBag("recipients.$i.email"); -// if ($self && Str::lower(trim((string)$this->recipients[$i]['email'])) === $self) { -// $this->addError("recipients.$i.email", 'Alias darf nicht an sich selbst weiterleiten.'); -// } -// } -// -// if ($name === 'type' && $this->type === 'single') { -// $this->recipients = [$this->recipients[0] ?? ['mail_user_id' => null, 'email' => null]]; -// } -// -// $this->ensureAtLeastOneRow(); -// $this->recomputeUi(); -// $this->validateRecipientDuplicates(); // feldgenaues Feedback live -// } -// -// public function updatedType(string $value): void -// { -// if ($value === 'single') { -// $first = $this->recipients[0] ?? ['mail_user_id' => null, 'email' => null]; -// $this->recipients = [$first]; -// $this->recomputeUi(); -// $this->validateRecipientDuplicates(); -// } -// } -// -// public function updatedDomainId(): void -// { -// $this->domain = $this->domainId -// ? Domain::where('is_system', false)->find($this->domainId) -// : null; -// -// $this->domainMailUsers = $this->domain -// ? MailUser::where('domain_id', $this->domain->id)->orderBy('localpart')->get() -// : collect(); -// -// // Zeilen resetten, weil Empfänger domaingebunden sind -// $this->recipients = [['mail_user_id' => null, 'email' => null]]; -// -// $this->ensureAtLeastOneRow(); -// $this->recomputeUi(); -// $this->validateRecipientDuplicates(); -// } -// -// public function addRecipientRow(): void -// { -// if ($this->type === 'single') return; -// if (count($this->recipients) >= $this->maxGroupRecipients) return; -// -// $this->recipients[] = ['mail_user_id' => null, 'email' => null]; -// $this->recomputeUi(); -// } -// -// public function removeRecipientRow(int $index): void -// { -// unset($this->recipients[$index]); -// $this->recipients = array_values($this->recipients); -// $this->ensureAtLeastOneRow(); -// $this->recomputeUi(); -// $this->validateRecipientDuplicates(); -// } -// -// // -------------------- Helpers -------------------- -// -// // vollständige Alias-Adresse (z.B. "info@pixio.at") oder null -// private function currentAliasAddress(): ?string -// { -// if (!$this->domainId || $this->local === '') return null; -// -// $domainName = $this->domain?->domain ?? Domain::find($this->domainId)?->domain; -// if (!$domainName) return null; -// -// return Str::lower(trim($this->local)) . '@' . Str::lower($domainName); -// } -// -// private function ensureAtLeastOneRow(): void -// { -// if (empty($this->recipients)) { -// $this->recipients = [['mail_user_id' => null, 'email' => null]]; -// } -// } -// -// // UI neu berechnen (inkl. Self-Loop-Schutz) -// private function recomputeUi(): void -// { -// $count = count($this->recipients); -// $this->canAddRecipient = $this->type === 'group' && $count < $this->maxGroupRecipients; -// -// // bereits gewählte interne IDs und externe Adressen einsammeln -// $usedMailboxIds = []; -// $usedExternalAddr = []; -// -// foreach ($this->recipients as $r) { -// if (!empty($r['mail_user_id'])) $usedMailboxIds[] = (int)$r['mail_user_id']; -// if (!empty($r['email'])) $usedExternalAddr[] = Str::lower(trim((string)$r['email'])); -// } -// -// $this->disabledMailboxIdsByRow = []; -// $this->rowState = []; -// -// // Aktuelle Alias-Adresse für Self-Loop-Checks -// $aliasAddr = $this->currentAliasAddress(); -// -// for ($i = 0; $i < $count; $i++) { -// $myId = (int)($this->recipients[$i]['mail_user_id'] ?? 0); -// $hasInternal = $myId > 0; -// $hasExternal = !empty($this->recipients[$i]['email']); -// $myExternal = Str::lower(trim((string)($this->recipients[$i]['email'] ?? ''))); -// -// // andere bereits gewählte internen IDs sperren -// $disabled = array_values(array_unique(array_filter( -// $usedMailboxIds, fn($id) => $id && $id !== $myId -// ))); -// -// // interne IDs zusätzlich sperren, deren Adresse in irgendeinem externen Feld steht -// if ($this->domain) { -// foreach ($this->domainMailUsers as $mu) { -// $addr = Str::lower($mu->localpart . '@' . $this->domain->domain); -// if (in_array($addr, $usedExternalAddr, true) && $mu->id !== $myId) { -// $disabled[] = (int)$mu->id; -// } -// } -// } -// -// $this->disabledMailboxIdsByRow[$i] = array_values(array_unique($disabled)); -// -// // Self-Loop verhindern: wenn extern == alias, internen Picker sperren -// $disableInternal = $hasExternal || ($aliasAddr && $aliasAddr === $myExternal); -// -// $this->rowState[$i] = [ -// 'disable_internal' => $disableInternal, // XOR + Self-Loop -// 'disable_external' => $hasInternal, // XOR -// 'can_remove' => !($this->type === 'single' && $count === 1), -// ]; -// } -// } -// -// // Duplikate feldgenau markieren (keine Exceptions) -// private function validateRecipientDuplicates(): bool -// { -// // komplette Empfänger-Fehler zurücksetzen -// $this->resetErrorBag(); -// -// // Map Zeile -> normalisierte Zieladresse (intern => local@domain, extern => email) -// $values = []; // [i => 'addr'] -// $byAddrIndices = []; // 'addr' => [i1,i2,...] -// -// foreach ($this->recipients as $i => $r) { -// $addr = null; -// -// if (!empty($r['mail_user_id'])) { -// if ($this->domain) { -// $u = MailUser::find((int)$r['mail_user_id']); -// if ($u) $addr = Str::lower($u->localpart . '@' . $this->domain->domain); -// } -// } elseif (!empty($r['email'])) { -// $addr = Str::lower(trim((string)$r['email'])); -// } -// -// if ($addr) { -// $values[$i] = $addr; -// $byAddrIndices[$addr] = $byAddrIndices[$addr] ?? []; -// $byAddrIndices[$addr][] = $i; -// } -// } -// -// $hasDupes = false; -// -// foreach ($byAddrIndices as $addr => $indices) { -// if (count($indices) <= 1) continue; -// $hasDupes = true; -// // alle betroffenen Felder markieren (feldgenau) -// foreach ($indices as $i) { -// if (!empty($this->recipients[$i]['mail_user_id'])) { -// $this->addError("recipients.$i.mail_user_id", 'Dieser Empfänger ist bereits hinzugefügt.'); -// } else { -// $this->addError("recipients.$i.email", 'Dieser Empfänger ist bereits hinzugefügt.'); -// } -// } -// } -// -// return $hasDupes; -// } -// -// private function aliasConflictsWithMailbox(): bool -// { -// if (!$this->domainId || $this->local === '') return false; -// -// $local = Str::lower(trim($this->local)); -// return MailUser::query() -// ->where('domain_id', $this->domainId) -// ->whereRaw('LOWER(localpart) = ?', [$local]) -// ->exists(); -// } -// -// // -------------------- Save -------------------- -// -// public function save(): void -// { -// // 1) Basisregeln -// $this->validate($this->rules(), $this->messages()); -// -// // 2) Zeilen dürfen nicht leer sein (weder intern noch extern) -// foreach ($this->recipients as $i => $r) { -// if (empty($r['mail_user_id']) && empty($r['email'])) { -// $this->addError("recipients.$i.email", 'Wähle internes Postfach oder externe E-Mail.'); -// return; -// } -// } -// -// // 3) Duplikate feldgenau prüfen -// if ($this->validateRecipientDuplicates()) { -// return; -// } -// -// // 3b) Self-Loop final abfangen -// if ($aliasAddr = $this->currentAliasAddress()) { -// foreach ($this->recipients as $i => $r) { -// // extern -// if (!empty($r['email']) && Str::lower(trim((string)$r['email'])) === $aliasAddr) { -// $this->addError("recipients.$i.email", 'Alias darf nicht an sich selbst weiterleiten.'); -// return; -// } -// // intern (falls je relevant) -// if (!empty($r['mail_user_id'])) { -// $u = MailUser::find((int)$r['mail_user_id']); -// if ($u && $this->domain && Str::lower($u->localpart . '@' . $this->domain->domain) === $aliasAddr) { -// $this->addError("recipients.$i.mail_user_id", 'Alias darf nicht an sich selbst weiterleiten.'); -// return; -// } -// } -// } -// } -// -// // 4) Alias-Adresse darf kein bestehendes Postfach sein -// if ($this->aliasConflictsWithMailbox()) { -// $this->addError('local', 'Diese Adresse ist bereits als Postfach vergeben.'); -// return; -// } -// -// // 5) Persistieren -// $rows = collect($this->recipients) -// ->map(fn($r) => [ -// 'mail_user_id' => $r['mail_user_id'] ?: null, -// 'email' => isset($r['email']) && $r['email'] !== '' ? Str::lower(trim($r['email'])) : null, -// ]) -// ->filter(fn($r) => $r['mail_user_id'] || $r['email']) -// ->values(); -// -// DB::transaction(function () use ($rows) { -// /** @var MailAlias $alias */ -// $alias = MailAlias::updateOrCreate( -// ['id' => $this->aliasId], -// [ -// 'domain_id' => $this->domainId, -// 'local' => $this->local, -// 'type' => $this->type, -// 'is_active' => $this->is_active, -// 'notes' => $this->notes, -// ] -// ); -// -// $alias->recipients()->delete(); -// foreach ($rows as $i => $r) { -// $alias->recipients()->create([ -// 'mail_user_id' => $r['mail_user_id'], -// 'email' => $r['email'], -// 'position' => $i, -// ]); -// } -// -// $this->aliasId = $alias->id; -// }); -// -// $this->dispatch('alias:' . ($this->aliasId ? 'updated' : 'created')); -// $this->dispatch('closeModal'); -// } -// -// // -------------------- Validation -------------------- -// -// protected function rules(): array -// { -// // blocke offensichtliche Kollisionen mit Mailboxen (gleiche Domain) -// $occupied = $this->domainId -// ? MailUser::query() -// ->where('domain_id', $this->domainId) -// ->pluck('localpart') -// ->map(fn($l) => Str::lower($l)) -// ->all() -// : []; -// -// return [ -// 'domainId' => ['required', 'exists:domains,id'], -// 'local' => [ -// 'required', -// 'regex:/^[A-Za-z0-9._%+-]+$/', -// Rule::unique('mail_aliases', 'local') -// ->ignore($this->aliasId) -// ->where(fn($q) => $q->where('domain_id', $this->domainId)), -// Rule::notIn($occupied), -// ], -// 'type' => ['required', 'in:single,group'], -// -// 'recipients' => ['array', 'min:1', 'max:' . ($this->type === 'single' ? 1 : $this->maxGroupRecipients)], -// 'recipients.*.mail_user_id' => ['nullable', 'exists:mail_users,id'], -// 'recipients.*.email' => ['nullable', 'email:rfc'], -// ]; -// } -// -// protected function messages(): array -// { -// return [ -// 'local.required' => 'Alias-Adresse ist erforderlich.', -// 'local.regex' => 'Erlaubt sind Buchstaben, Zahlen und ._%+-', -// 'local.unique' => 'Dieser Alias existiert in dieser Domain bereits.', -// 'local.not_in' => 'Diese Adresse ist bereits als Postfach vergeben.', -// 'recipients.min' => 'Mindestens ein Empfänger ist erforderlich.', -// 'recipients.max' => $this->type === 'single' -// ? 'Bei „Single“ ist nur ein Empfänger zulässig.' -// : 'Maximal ' . $this->maxGroupRecipients . ' Empfänger erlaubt.', -// ]; -// } -// -// public function render() -// { -// return view('livewire.ui.mail.modal.alias-form-modal', [ -// 'domains' => Domain::where('is_system', false) -// ->orderBy('domain') -// ->get(['id', 'domain']), -// ]); -// } -//} - - - -//namespace App\Livewire\Ui\Mail\Modal; -// -//use App\Models\Domain; -//use App\Models\MailAlias; -//use App\Models\MailUser; -//use Illuminate\Support\Facades\DB; -//use Illuminate\Support\Str; -//use Illuminate\Validation\Rule; -//use LivewireUI\Modal\ModalComponent; -// -//class AliasFormModal extends ModalComponent -//{ -// // Eingabe / State -// public ?int $aliasId = null; -// public ?int $domainId = null; -// public string $local = ''; -// public string $type = 'single'; // single|group -// public bool $is_active = true; -// public ?string $notes = null; -// -// // Empfänger-Zeilen (each: ['mail_user_id'=>?int, 'email'=>?string]) -// public array $recipients = []; -// -// // UI-Steuerung (nur Ausgabe im Blade) -// public int $maxGroupRecipients = 20; -// public bool $canAddRecipient = false; // „+ Empfänger“ erlauben -// public array $rowState = []; // [i => ['disable_internal'=>bool,'disable_external'=>bool,'can_remove'=>bool]] -// public array $disabledMailboxIdsByRow = []; // [i => [ids...]] -// -// // Lookup -// public ?Domain $domain = null; -// /** @var \Illuminate\Support\Collection */ -// public $domainMailUsers; -// -// // -------------------- Lifecycle -------------------- -// -// public function mount(?int $aliasId = null, ?int $domainId = null): void -// { -// $this->aliasId = $aliasId; -// $this->domainId = $domainId; -// -// if ($this->aliasId) { -// $alias = MailAlias::with(['domain','recipients.mailUser'])->findOrFail($this->aliasId); -// if ($alias->domain->is_system) { -// abort(403, 'System-Domains sind für Aliasse gesperrt.'); -// } -// $this->domain = $alias->domain; -// $this->domainId = $alias->domain_id; -// -// $this->fill([ -// 'local' => $alias->local, -// 'type' => $alias->type, -// 'is_active' => $alias->is_active, -// 'notes' => $alias->notes, -// ]); -// -// $this->recipients = $alias->recipients -// ->map(fn($r) => ['mail_user_id' => $r->mail_user_id, 'email' => $r->email]) -// ->all(); -// } else { -// // erste Nicht-System-Domain -// $this->domain = $this->domainId -// ? Domain::where('is_system', false)->findOrFail($this->domainId) -// : Domain::where('is_system', false)->orderBy('domain')->first(); -// -// $this->domainId = $this->domain->id ?? null; -// $this->recipients = [['mail_user_id' => null, 'email' => null]]; -// } -// -// $this->domainMailUsers = $this->domain -// ? MailUser::where('domain_id', $this->domain->id)->orderBy('localpart')->get() -// : collect(); -// -// $this->ensureAtLeastOneRow(); -// $this->recomputeUi(); -// $this->validateRecipientDuplicates(); // sofortige Feldfehler bei vorbefüllten Daten -// } -// -// // -------------------- UI-Reaktionen -------------------- -// -// public function updated($name, $value): void -// { -// // XOR: wenn intern gesetzt -> extern leeren, und umgekehrt -// if (preg_match('/^recipients\.(\d+)\.(mail_user_id|email)$/', $name, $m)) { -// $i = (int)$m[1]; -// if ($m[2] === 'mail_user_id' && !empty($value)) { -// $this->recipients[$i]['email'] = null; -// } elseif ($m[2] === 'email') { -// $this->recipients[$i]['mail_user_id'] = null; -// $this->recipients[$i]['email'] = Str::lower(trim((string)$this->recipients[$i]['email'])); -// } -// } -// -// if (preg_match('/^recipients\.(\d+)\.email$/', $name, $m)) { -// $i = (int)$m[1]; -// $self = $this->currentAliasAddress(); -// $this->resetErrorBag("recipients.$i.email"); -// if ($self && \Illuminate\Support\Str::lower(trim((string)$this->recipients[$i]['email'])) === $self) { -// $this->addError("recipients.$i.email", 'Alias darf nicht an sich selbst weiterleiten.'); -// } -// } -// -// if ($name === 'type' && $this->type === 'single') { -// $this->recipients = [ $this->recipients[0] ?? ['mail_user_id'=>null,'email'=>null] ]; -// } -// -// $this->ensureAtLeastOneRow(); -// $this->recomputeUi(); -// $this->validateRecipientDuplicates(); // feldgenaues Feedback live -// } -// -// public function updatedType(string $value): void -// { -// if ($value === 'single') { -// $first = $this->recipients[0] ?? ['mail_user_id'=>null,'email'=>null]; -// $this->recipients = [ $first ]; -// $this->recomputeUi(); -// $this->validateRecipientDuplicates(); -// } -// } -// -// public function updatedDomainId(): void -// { -// $this->domain = $this->domainId -// ? Domain::where('is_system', false)->find($this->domainId) -// : null; -// -// $this->domainMailUsers = $this->domain -// ? MailUser::where('domain_id', $this->domain->id)->orderBy('localpart')->get() -// : collect(); -// -// // Zeilen resetten, weil Empfänger domaingebunden sind -// $this->recipients = [['mail_user_id' => null, 'email' => null]]; -// -// $this->ensureAtLeastOneRow(); -// $this->recomputeUi(); -// $this->validateRecipientDuplicates(); -// } -// -// public function addRecipientRow(): void -// { -// if ($this->type === 'single') return; -// if (count($this->recipients) >= $this->maxGroupRecipients) return; -// -// $this->recipients[] = ['mail_user_id'=>null,'email'=>null]; -// $this->recomputeUi(); -// // kein Duplicate hier, weil leer -// } -// -// public function removeRecipientRow(int $index): void -// { -// unset($this->recipients[$index]); -// $this->recipients = array_values($this->recipients); -// $this->ensureAtLeastOneRow(); -// $this->recomputeUi(); -// $this->validateRecipientDuplicates(); -// } -// -// // -------------------- Helpers -------------------- -// -// private function currentAliasAddress(): ?string -// { -// if (!$this->domainId || $this->local === '' || !$this->domain) return null; -// return \Illuminate\Support\Str::lower(trim($this->local).'@'.$this->domain->domain); -// } -// -// private function ensureAtLeastOneRow(): void -// { -// if (empty($this->recipients)) { -// $this->recipients = [['mail_user_id'=>null,'email'=>null]]; -// } -// } -// -// private function recomputeUi(): void -// { -// $count = count($this->recipients); -// $this->canAddRecipient = $this->type === 'group' && $count < $this->maxGroupRecipients; -// -// // bereits gewählte interne IDs und externe Adressen einsammeln -// $usedMailboxIds = []; -// $usedExternalAddrs = []; // ['a@b.tld', ...] normalisiert -// -// foreach ($this->recipients as $r) { -// if (!empty($r['mail_user_id'])) $usedMailboxIds[] = (int)$r['mail_user_id']; -// if (!empty($r['email'])) $usedExternalAddrs[] = Str::lower(trim((string)$r['email'])); -// } -// -// $this->disabledMailboxIdsByRow = []; -// $this->rowState = []; -// -// // Helper: internes Postfach -> vollständige Adresse -// $mailboxEmail = function (?int $id): ?string { -// if (!$id || !$this->domain) return null; -// $u = MailUser::find($id); -// return $u ? Str::lower($u->localpart.'@'.$this->domain->domain) : null; -// }; -// -// for ($i = 0; $i < $count; $i++) { -// $myId = (int)($this->recipients[$i]['mail_user_id'] ?? 0); -// $hasInternal = $myId > 0; -// $hasExternal = !empty($this->recipients[$i]['email']); -// -// // Basis: andere bereits gewählte interne IDs sperren -// $disabled = array_values(array_unique(array_filter( -// $usedMailboxIds, fn($id) => $id && $id !== $myId -// ))); -// -// // Zusätzlich: interne IDs sperren, deren Adresse in irgendeinem externen Feld steht -// if ($this->domain) { -// foreach ($this->domainMailUsers as $mu) { -// $addr = Str::lower($mu->localpart.'@'.$this->domain->domain); -// if (in_array($addr, $usedExternalAddrs, true) && $mu->id !== $myId) { -// $disabled[] = (int)$mu->id; -// } -// } -// } -// -// $this->disabledMailboxIdsByRow[$i] = array_values(array_unique($disabled)); -// -// $this->rowState[$i] = [ -// 'disable_internal' => $hasExternal, // XOR -// 'disable_external' => $hasInternal, // XOR -// 'can_remove' => !($this->type === 'single' && $count === 1), -// ]; -// } -// } -// -// -// // Duplikate feldgenau markieren (keine Exceptions) -// private function validateRecipientDuplicates(): bool -// { -// // komplette Empfänger-Fehler zurücksetzen -// $this->resetErrorBag(); -// -// // Map Zeile -> normalisierte Zieladresse (intern => local@domain, extern => email) -// $values = []; // [i => 'addr'] -// $byAddrIndices = []; // 'addr' => [i1,i2,...] -// -// foreach ($this->recipients as $i => $r) { -// $addr = null; -// -// if (!empty($r['mail_user_id'])) { -// if ($this->domain) { -// $u = MailUser::find((int)$r['mail_user_id']); -// if ($u) $addr = Str::lower($u->localpart.'@'.$this->domain->domain); -// } -// } elseif (!empty($r['email'])) { -// $addr = Str::lower(trim((string)$r['email'])); -// } -// -// if ($addr) { -// $values[$i] = $addr; -// $byAddrIndices[$addr] = $byAddrIndices[$addr] ?? []; -// $byAddrIndices[$addr][] = $i; -// } -// } -// -// $hasDupes = false; -// -// foreach ($byAddrIndices as $addr => $indices) { -// if (count($indices) <= 1) continue; -// $hasDupes = true; -// // alle betroffenen Felder markieren (feldgenau) -// foreach ($indices as $i) { -// if (!empty($this->recipients[$i]['mail_user_id'])) { -// $this->addError("recipients.$i.mail_user_id", 'Dieser Empfänger ist bereits hinzugefügt.'); -// } else { -// $this->addError("recipients.$i.email", 'Dieser Empfänger ist bereits hinzugefügt.'); -// } -// } -// } -// -// return $hasDupes; -// } -// -// private function aliasConflictsWithMailbox(): bool -// { -// if (!$this->domainId || $this->local === '') return false; -// -// $local = Str::lower(trim($this->local)); -// return MailUser::query() -// ->where('domain_id', $this->domainId) -// ->whereRaw('LOWER(localpart) = ?', [$local]) -// ->exists(); -// } -// -// // -------------------- Save -------------------- -// -// public function save(): void -// { -// // 1) Basisregeln -// $this->validate($this->rules(), $this->messages()); -// -// // 2) Zeilen dürfen nicht leer sein (weder intern noch extern) -// foreach ($this->recipients as $i => $r) { -// if (empty($r['mail_user_id']) && empty($r['email'])) { -// $this->addError("recipients.$i.email", 'Wähle internes Postfach oder externe E-Mail.'); -// return; -// } -// } -// -// // 3) Duplikate feldgenau prüfen -// if ($this->validateRecipientDuplicates()) { -// return; -// } -// -// if ($aliasAddr = $this->currentAliasAddress()) { -// foreach ($this->recipients as $i => $r) { -// // extern -// if (!empty($r['email']) && \Illuminate\Support\Str::lower(trim((string)$r['email'])) === $aliasAddr) { -// $this->addError("recipients.$i.email", 'Alias darf nicht an sich selbst weiterleiten.'); -// return; -// } -// // intern (falls es später mal interne Empfänger ohne existierendes Postfach geben sollte) -// if (!empty($r['mail_user_id'])) { -// $u = \App\Models\MailUser::find((int)$r['mail_user_id']); -// if ($u && $this->domain && \Illuminate\Support\Str::lower($u->localpart.'@'.$this->domain->domain) === $aliasAddr) { -// $this->addError("recipients.$i.mail_user_id", 'Alias darf nicht an sich selbst weiterleiten.'); -// return; -// } -// } -// } -// } -// -// // 4) Alias-Adresse darf kein bestehendes Postfach sein -// if ($this->aliasConflictsWithMailbox()) { -// $this->addError('local', 'Diese Adresse ist bereits als Postfach vergeben.'); -// return; -// } -// -// // 5) Persistieren -// $rows = collect($this->recipients) -// ->map(fn($r) => [ -// 'mail_user_id' => $r['mail_user_id'] ?: null, -// 'email' => isset($r['email']) && $r['email'] !== '' ? Str::lower(trim($r['email'])) : null, -// ]) -// ->filter(fn($r) => $r['mail_user_id'] || $r['email']) -// ->values(); -// -// DB::transaction(function () use ($rows) { -// /** @var MailAlias $alias */ -// $alias = MailAlias::updateOrCreate( -// ['id' => $this->aliasId], -// [ -// 'domain_id' => $this->domainId, -// 'local' => $this->local, -// 'type' => $this->type, -// 'is_active' => $this->is_active, -// 'notes' => $this->notes, -// ] -// ); -// -// $alias->recipients()->delete(); -// foreach ($rows as $i => $r) { -// $alias->recipients()->create([ -// 'mail_user_id' => $r['mail_user_id'], -// 'email' => $r['email'], -// 'position' => $i, -// ]); -// } -// -// $this->aliasId = $alias->id; -// }); -// -// $this->dispatch('alias:' . ($this->aliasId ? 'updated' : 'created')); -// $this->dispatch('closeModal'); -// } -// -// // -------------------- Validation -------------------- -// -// protected function rules(): array -// { -// // blocke offensichtliche Kollisionen mit Mailboxen (gleiche Domain) -// $occupied = $this->domainId -// ? MailUser::query() -// ->where('domain_id', $this->domainId) -// ->pluck('localpart') -// ->map(fn($l) => Str::lower($l)) -// ->all() -// : []; -// -// return [ -// 'domainId' => ['required','exists:domains,id'], -// 'local' => [ -// 'required', -// 'regex:/^[A-Za-z0-9._%+-]+$/', -// Rule::unique('mail_aliases','local') -// ->ignore($this->aliasId) -// ->where(fn($q) => $q->where('domain_id', $this->domainId)), -// Rule::notIn($occupied), -// ], -// 'type' => ['required','in:single,group'], -// -// 'recipients' => ['array','min:1','max:'.($this->type==='single' ? 1 : $this->maxGroupRecipients)], -// 'recipients.*.mail_user_id' => ['nullable','exists:mail_users,id'], -// 'recipients.*.email' => ['nullable','email:rfc'], -// ]; -// } -// -// protected function messages(): array -// { -// return [ -// 'local.required' => 'Alias-Adresse ist erforderlich.', -// 'local.regex' => 'Erlaubt sind Buchstaben, Zahlen und ._%+-', -// 'local.unique' => 'Dieser Alias existiert in dieser Domain bereits.', -// 'local.not_in' => 'Diese Adresse ist bereits als Postfach vergeben.', -// 'recipients.min' => 'Mindestens ein Empfänger ist erforderlich.', -// 'recipients.max' => $this->type === 'single' -// ? 'Bei „Single“ ist nur ein Empfänger zulässig.' -// : 'Maximal '.$this->maxGroupRecipients.' Empfänger erlaubt.', -// ]; -// } -// -// public function render() -// { -// return view('livewire.ui.mail.modal.alias-form-modal', [ -// 'domains' => Domain::where('is_system', false) -// ->orderBy('domain') -// ->get(['id','domain']), -// ]); -// } -//} - - -// -//namespace App\Livewire\Ui\Mail\Modal; -// -//use App\Models\Domain; -//use App\Models\MailAlias; -//use App\Models\MailAliasRecipient; -//use App\Models\MailUser; -//use Illuminate\Support\Facades\DB; -//use Illuminate\Support\Str; -//use Illuminate\Validation\Rule; -//use Livewire\Attributes\On; -//use LivewireUI\Modal\ModalComponent; -// -//class AliasFormModal extends ModalComponent -//{ -// // Eingabe-Props -// public ?int $aliasId = null; // wenn gesetzt -> Edit -// public ?int $domainId = null; // Create: vorbelegen (oder Auswahl) -// public string $local = ''; -// public string $type = 'single'; // 'single' | 'group' -// public bool $is_active = true; -// public ?string $notes = null; -// public int $maxGroupRecipients = 20; -// public bool $canAddRecipient = false; -// public array $rowState = []; -// public array $disabledMailboxIdsByRow = []; -// -// // optionale UI-Fehler pro Zeile (Anzeige) -// public array $rowDuplicateError = []; -// -// public array $recipients = []; -// -// // UI-Daten -// public ?Domain $domain = null; -// /** @var \Illuminate\Support\Collection */ -// public $domainMailUsers; -// -// public function mount(?int $aliasId = null, ?int $domainId = null): void -// { -// $this->aliasId = $aliasId; -// $this->domainId = $domainId; -// -// if ($this->aliasId) { -// $alias = MailAlias::with(['domain','recipients.mailUser'])->findOrFail($this->aliasId); -// if ($alias->domain->is_system) { -// abort(403, 'System-Domains sind für Aliasse gesperrt.'); -// } -// $this->domain = $alias->domain; -// $this->domainId = $alias->domain_id; -// -// $this->fill([ -// 'local' => $alias->local, -// 'type' => $alias->type, -// 'is_active' => $alias->is_active, -// 'notes' => $alias->notes, -// ]); -// -// $this->recipients = $alias->recipients->map(fn($r) => [ -// 'mail_user_id' => $r->mail_user_id, -// 'email' => $r->email, -// ])->all(); -// } else { -// // Erst NICHT-System-Domain -// $this->domain = $this->domainId -// ? Domain::where('is_system', false)->findOrFail($this->domainId) -// : Domain::where('is_system', false)->orderBy('domain')->first(); -// -// $this->domainId = $this->domain->id ?? null; -// $this->recipients = [['mail_user_id' => null, 'email' => null]]; -// } -// -// $this->domainMailUsers = $this->domain -// ? MailUser::where('domain_id', $this->domain->id)->orderBy('localpart')->get() -// : collect(); -// -// $this->ensureAtLeastOneRow(); -// $this->recomputeUi(); -// } -// -// public function updatedType(string $value): void -// { -// if ($value === 'single') { -// // exakt eine Zeile behalten -// $first = $this->recipients[0] ?? ['mail_user_id'=>null,'email'=>null]; -// $this->recipients = [ $first ]; -// } -// } -// -// private function validateRecipientDuplicates(): bool -// { -// // vorhandene Feldfehler für Empfänger leeren -// $this->resetErrorBag('recipients'); -// -// // Map: Zielstring je Zeile -> Anzahl -// $values = []; // [i => 'office@pixio.at'] -// $counts = []; // 'office@pixio.at' => n -// -// foreach ($this->recipients as $i => $r) { -// $val = null; -// -// if (!empty($r['mail_user_id'])) { -// $u = MailUser::find((int)$r['mail_user_id']); -// if ($u && $this->domain) { -// $val = Str::lower($u->localpart.'@'.$this->domain->domain); -// } -// } elseif (!empty($r['email'])) { -// $val = Str::lower(trim((string)$r['email'])); -// } -// -// if ($val) { -// $values[$i] = $val; -// $counts[$val] = ($counts[$val] ?? 0) + 1; -// } -// } -// -// $hasDupes = false; -// -// foreach ($values as $i => $val) { -// if (($counts[$val] ?? 0) > 1) { -// // Feldgenau markieren -// if (!empty($this->recipients[$i]['mail_user_id'])) { -// $this->addError("recipients.$i.mail_user_id", 'Dieser Empfänger ist bereits hinzugefügt.'); -// } else { -// $this->addError("recipients.$i.email", 'Dieser Empfänger ist bereits hinzugefügt.'); -// } -// $hasDupes = true; -// } -// } -// -// return $hasDupes; -// } -// -// -// // Livewire v3: universeller Hook für verschachtelte Felder -// public function updated($name, $value): void -// { -// // XOR je Zeile -// if (preg_match('/^recipients\.(\d+)\.(mail_user_id|email)$/', $name, $m)) { -// $i = (int)$m[1]; -// if ($m[2] === 'mail_user_id' && !empty($value)) { -// $this->recipients[$i]['email'] = null; -// } elseif ($m[2] === 'email' && !empty($value)) { -// $this->recipients[$i]['mail_user_id'] = null; -// $this->recipients[$i]['email'] = Str::lower(trim((string)$this->recipients[$i]['email'])); -// } -// } -// -// if ($name === 'type' && $this->type === 'single') { -// $this->recipients = [ $this->recipients[0] ?? ['mail_user_id'=>null,'email'=>null] ]; -// } -// -// $this->ensureAtLeastOneRow(); -// $this->recomputeUi(); -// -// // sofortiges, feldgenaues Duplikat-Feedback -// $this->validateRecipientDuplicates(); -// } -// -// public function addRecipientRow(): void -// { -// if ($this->type === 'single') return; -// if (count($this->recipients) >= $this->maxGroupRecipients) return; -// -// $this->recipients[] = ['mail_user_id'=>null,'email'=>null]; -// $this->recomputeUi(); -// } -// -// public function updatedDomainId(): void -// { -// $this->domain = $this->domainId -// ? Domain::where('is_system', false)->find($this->domainId) -// : null; -// -// $this->domainMailUsers = $this->domain -// ? MailUser::where('domain_id', $this->domain->id)->orderBy('localpart')->get() -// : collect(); -// -// $this->recipients = [['mail_user_id' => null, 'email' => null]]; -// -// $this->ensureAtLeastOneRow(); -// $this->recomputeUi(); -// } -// -// -// public function removeRecipientRow(int $index): void -// { -// unset($this->recipients[$index]); -// $this->recipients = array_values($this->recipients); -// $this->ensureAtLeastOneRow(); -// $this->recomputeUi(); -// } -// -// private function ensureAtLeastOneRow(): void -// { -// if (empty($this->recipients)) { -// $this->recipients = [['mail_user_id'=>null,'email'=>null]]; -// } -// } -// -// private function recomputeUi(): void -// { -// $count = count($this->recipients); -// -// // Button „+ Empfänger“ -// $this->canAddRecipient = $this->type === 'group' && $count < $this->maxGroupRecipients; -// -// // Sammle bereits gewählte interne Mailbox-IDs und externe E-Mails (normalisiert) -// $usedMailboxIds = []; -// $usedExternal = []; -// -// foreach ($this->recipients as $i => $r) { -// if (!empty($r['mail_user_id'])) $usedMailboxIds[] = (int)$r['mail_user_id']; -// if (!empty($r['email'])) $usedExternal[] = Str::lower(trim((string)$r['email'])); -// } -// -// // pro Zeile: welche Mailbox-IDs sollen disabled sein (alle used außer die eigene) -// $this->disabledMailboxIdsByRow = []; -// $this->rowState = []; -// $this->rowDuplicateError = []; -// -// for ($i=0; $i<$count; $i++) { -// $myId = (int)($this->recipients[$i]['mail_user_id'] ?? 0); -// $myEmail = Str::lower(trim((string)($this->recipients[$i]['email'] ?? ''))); -// -// $disabled = array_values(array_unique(array_filter($usedMailboxIds, fn($id) => $id && $id !== $myId))); -// $this->disabledMailboxIdsByRow[$i] = $disabled; -// -// $hasInternal = $myId > 0; -// $hasExternal = $myEmail !== ''; -// -// $this->rowState[$i] = [ -// 'disable_internal' => $hasExternal, // XOR -// 'disable_external' => $hasInternal, // XOR -// 'can_remove' => !($this->type === 'single' && $count === 1), -// ]; -// -// // Duplikats-UI: wenn meine Zieladresse mehrfach vorkommt -> Fehlermeldung für diese Zeile -// if ($hasInternal && count(array_filter($usedMailboxIds, fn($id) => $id === $myId)) > 1) { -// $this->rowDuplicateError[$i] = 'Dieses Postfach ist bereits Empfänger.'; -// } -// if ($hasExternal && count(array_filter($usedExternal, fn($e) => $e === $myEmail)) > 1) { -// $this->rowDuplicateError[$i] = 'Diese E-Mail ist bereits Empfänger.'; -// } -// } -// -// $this->hasDuplicatesAndMarkUi(); -// } -// -//// private function assertNoDuplicates(): void -//// { -//// // Ziel-Strings bilden: intern -> localpart@domain, extern -> E-Mail -//// $targets = []; -//// foreach ($this->recipients as $r) { -//// if (!empty($r['mail_user_id'])) { -//// $u = MailUser::find((int)$r['mail_user_id']); -//// if ($u && $this->domain) { -//// $targets[] = Str::lower($u->localpart.'@'.$this->domain->domain); -//// } -//// } elseif (!empty($r['email'])) { -//// $targets[] = Str::lower(trim((string)$r['email'])); -//// } -//// } -//// $dupes = array_unique(array_diff_key($targets, array_unique($targets))); -//// if (!empty($dupes)) { -//// $this->addError('recipients', 'Doppelte Empfänger nicht erlaubt.'); -//// throw new \RuntimeException('duplicates'); // brich Save clean ab -//// } -//// } -// -// private function hasDuplicatesAndMarkUi(): bool -// { -// // Ziel-String pro Zeile: intern => local@domain, extern => email -// $targets = []; // [i => 'office@pixio.at'] -// $byValueCount = []; // 'office@pixio.at' => n -// -// $this->rowDuplicateError = []; // UI reset -// -// foreach ($this->recipients as $i => $r) { -// $val = null; -// -// if (!empty($r['mail_user_id'])) { -// $u = MailUser::find((int)$r['mail_user_id']); -// if ($u && $this->domain) { -// $val = mb_strtolower($u->localpart.'@'.$this->domain->domain); -// } -// } elseif (!empty($r['email'])) { -// $val = mb_strtolower(trim((string)$r['email'])); -// } -// -// if ($val) { -// $targets[$i] = $val; -// $byValueCount[$val] = ($byValueCount[$val] ?? 0) + 1; -// } -// } -// -// $hasDupes = false; -// foreach ($targets as $i => $val) { -// if (($byValueCount[$val] ?? 0) > 1) { -// $this->rowDuplicateError[$i] = 'Dieser Empfänger ist bereits hinzugefügt.'; -// $hasDupes = true; -// } -// } -// -// return $hasDupes; -// } -// -// -// -// public function save(): void -// { -// $this->validate($this->rules(), $this->messages()); -// -// // XOR-Check Empfänger + Single/Group-Logik -// $rows = collect($this->recipients) -// ->map(fn($r) => [ -// 'mail_user_id' => $r['mail_user_id'] ?: null, -// 'email' => isset($r['email']) && $r['email'] !== '' ? mb_strtolower(trim($r['email'])) : null, -// ]) -// ->filter(fn($r) => $r['mail_user_id'] || $r['email']) -// ->values(); -// -// if ($rows->isEmpty()) { -// $this->addError('recipients', 'Mindestens ein Empfänger erforderlich.'); -// return; -// } -// if ($this->type === 'single' && $rows->count() !== 1) { -// $this->addError('recipients', 'Bei Single-Alias ist genau ein Empfänger erlaubt.'); -// return; -// } -// -// foreach ($this->recipients as $i => $r) { -// if (empty($r['mail_user_id']) && empty($r['email'])) { -// $this->addError("recipients.$i", 'Wähle internes Postfach oder externe E-Mail.'); -// return; -// } -// } -// -// if ($this->validateRecipientDuplicates()) { -// return; // freundlich abbrechen, keine Exception -// } -// -// // 4) Alias-Adresse vs. bestehende Mailboxen (siehe Teil B) -// if ($this->aliasConflictsWithMailbox()) { -// $this->addError('local', 'Diese Adresse ist bereits als Postfach vergeben.'); -// return; -// } -// -// -// DB::transaction(function () use ($rows) { -// /** @var MailAlias $alias */ -// $alias = MailAlias::updateOrCreate( -// ['id' => $this->aliasId], -// [ -// 'domain_id' => $this->domainId, -// 'local' => $this->local, -// 'type' => $this->type, -// 'is_active' => $this->is_active, -// 'notes' => $this->notes, -// ] -// ); -// -// // Empfänger neu schreiben (einfach & robust) -// $alias->recipients()->delete(); -// foreach ($rows as $i => $r) { -// $alias->recipients()->create([ -// 'mail_user_id' => $r['mail_user_id'], -// 'email' => $r['email'], -// 'position' => $i, -// ]); -// } -// -// $this->aliasId = $alias->id; -// }); -// -// $this->dispatch('alias:' . ($this->aliasId ? 'updated' : 'created')); -// $this->dispatch('closeModal'); -// } -// -// private function aliasConflictsWithMailbox(): bool -// { -// if (!$this->domainId || $this->local === '') return false; -// -// $local = Str::lower(trim($this->local)); -// -// return MailUser::query() -// ->where('domain_id', $this->domainId) -// ->whereRaw('LOWER(localpart) = ?', [$local]) -// ->exists(); -// } -// -// protected function rules(): array -// { -// $occupied = MailUser::query() -// ->where('domain_id', $this->domainId) -// ->pluck('localpart') -// ->map(fn($l) => Str::lower($l)) -// ->all(); -// -// return [ -// 'domainId' => ['required','exists:domains,id'], -// 'local' => [ -// 'required', -// 'regex:/^[A-Za-z0-9._%+-]+$/', -// Rule::unique('mail_aliases','local') -// ->ignore($this->aliasId) -// ->where(fn($q) => $q->where('domain_id', $this->domainId)), -// Rule::notIn($occupied), // verhindert offensichtliche Kollisionen -// ], -// 'type' => ['required','in:single,group'], -// 'recipients' => ['array','min:1','max:'.($this->type==='single'?1:$this->maxGroupRecipients)], -// 'recipients.*.mail_user_id' => ['nullable','exists:mail_users,id'], -// 'recipients.*.email' => ['nullable','email:rfc'], -// ]; -// } -// -// -// protected function messages(): array -// { -// return [ -// 'local.not_in' => 'Diese Adresse ist bereits als Postfach vergeben.', -// 'local.unique' => 'Dieser Alias existiert in dieser Domain bereits.', -// 'local.regex' => 'Erlaubt sind Buchstaben, Zahlen und ._%+-', -// 'recipients.min' => 'Mindestens ein Empfänger ist erforderlich.', -// 'recipients.max' => $this->type === 'single' -// ? 'Bei „Single“ ist nur ein Empfänger zulässig.' -// : 'Maximal '.$this->maxGroupRecipients.' Empfänger erlaubt.', -// ]; -// } -// -// -// public function render() -// { -// return view('livewire.ui.mail.modal.alias-form-modal', [ -// 'domains' => Domain::where('is_system', false)->orderBy('domain')->get(['id','domain']), -// ]); -// } -//} diff --git a/app/Livewire/Ui/Mail/Modal/MailboxCreateModal.php b/app/Livewire/Ui/Mail/Modal/MailboxCreateModal.php index 36e2f0a..9cbb77e 100644 --- a/app/Livewire/Ui/Mail/Modal/MailboxCreateModal.php +++ b/app/Livewire/Ui/Mail/Modal/MailboxCreateModal.php @@ -92,7 +92,7 @@ class MailboxCreateModal extends ModalComponent { // alle Nicht-System-Domains in Select $this->domains = Domain::query() - ->where('is_system', false) + ->where('is_system', false)->where('is_server', false) ->orderBy('domain')->get(['id', 'domain'])->toArray(); // vorselektieren falls mitgegeben, sonst 1. Domain (falls vorhanden) @@ -291,251 +291,3 @@ class MailboxCreateModal extends ModalComponent return view('livewire.ui.mail.modal.mailbox-create-modal'); } } - -//namespace App\Livewire\Ui\Mail\Modal; -// -//use App\Models\Domain; -//use App\Models\MailUser; -//use Illuminate\Database\QueryException; -//use Illuminate\Support\Facades\Hash; -//use Illuminate\Validation\Rule; -//use Livewire\Attributes\On; -//use LivewireUI\Modal\ModalComponent; -// -//class MailboxCreateModal extends ModalComponent -//{ -// // optional vorselektierte Domain -// public ?int $domain_id = null; -// -// // Anzeige -// public string $domain_name = ''; -// /** @var array */ -// public array $domains = []; -// public string $email_preview = ''; -// -// public string $localpart = ''; -// public ?string $display_name = null; -// public ?string $password = null; -// public int $quota_mb = 0; -// public ?int $rate_limit_per_hour = null; -// public bool $is_active = true; -// public bool $must_change_pw = true; -// -// // Limits / Status -// public ?int $limit_max_mailboxes = null; -// public ?int $limit_default_quota_mb = null; -// public ?int $limit_max_quota_per_mb = null; -// public ?int $limit_total_quota_mb = null; // 0 = unlimitiert -// public ?int $limit_domain_rate_per_hour = null; -// public bool $allow_rate_limit_override = false; -// -// public int $mailbox_count_used = 0; -// public int $domain_storage_used_mb = 0; -// -// // Hints/Flags -// public string $quota_hint = ''; -// public bool $rate_limit_readonly = false; -// public bool $no_mailbox_slots = false; -// public bool $no_storage_left = false; -// public bool $can_create = true; -// public string $block_reason = ''; -// -// /* ---------- Validation ---------- */ -// protected function rules(): array -// { -// $maxPerMailbox = $this->limit_max_quota_per_mb ?? PHP_INT_MAX; -// $remainingByTotal = (is_null($this->limit_total_quota_mb) || (int)$this->limit_total_quota_mb === 0) -// ? PHP_INT_MAX -// : max(0, (int)$this->limit_total_quota_mb - (int)$this->domain_storage_used_mb); -// $cap = min($maxPerMailbox, $remainingByTotal); -// -// return [ -// 'domain_id' => ['required', Rule::exists('domains', 'id')], -// 'localpart' => [ -// 'required', 'max:191', 'regex:/^[A-Za-z0-9._%+-]+$/', -// Rule::unique('mail_users', 'localpart')->where(fn($q) => $q->where('domain_id', $this->domain_id)), -// ], -// 'display_name' => ['nullable', 'max:191'], -// 'password' => ['nullable', 'min:8'], -// 'quota_mb' => ['required', 'integer', 'min:0', 'max:' . $cap], -// 'rate_limit_per_hour' => ['nullable', 'integer', 'min:1'], -// 'is_active' => ['boolean'], -// 'must_change_pw' => ['boolean'], -// ]; -// } -// -// /* ---------- Lifecycle ---------- */ -// public function mount(?int $domainId = null): void -// { -// // alle Nicht-System-Domains in Select -// $this->domains = Domain::query() -// ->where('is_system', false) -// ->orderBy('domain')->get(['id', 'domain'])->toArray(); -// -// // vorselektieren falls mitgegeben, sonst 1. Domain (falls vorhanden) -// $this->domain_id = $domainId ?: ($this->domains[0]['id'] ?? null); -// -// // Limits + Anzeige laden -// $this->syncDomainContext(); -// } -// -// public function updatedDomainId(): void -// { -// $this->resetErrorBag(); // scoped unique etc. -// $this->syncDomainContext(); -// } -// -// public function updatedLocalpart(): void -// { -// $this->localpart = strtolower(trim($this->localpart)); -// $this->rebuildEmailPreview(); -// } -// -// public function updatedQuotaMb(): void -// { -// $this->recomputeQuotaHints(); -// $this->recomputeBlockers(); -// } -// -// /* ---------- Helpers ---------- */ -// private function syncDomainContext(): void -// { -// if (!$this->domain_id) return; -// -// $d = Domain::query() -// ->withCount('mailUsers') -// ->withSum('mailUsers as used_storage_mb', 'quota_mb') -// ->findOrFail($this->domain_id); -// -// $this->domain_name = $d->domain; -// $this->limit_max_mailboxes = (int)$d->max_mailboxes; -// $this->limit_default_quota_mb = (int)$d->default_quota_mb; -// $this->limit_max_quota_per_mb = $d->max_quota_per_mailbox_mb !== null ? (int)$d->max_quota_per_mailbox_mb : null; -// $this->limit_total_quota_mb = (int)$d->total_quota_mb; // 0 = unlimitiert -// $this->limit_domain_rate_per_hour = $d->rate_limit_per_hour !== null ? (int)$d->rate_limit_per_hour : null; -// $this->allow_rate_limit_override = (bool)$d->rate_limit_override; -// -// $this->mailbox_count_used = (int)$d->mail_users_count; -// $this->domain_storage_used_mb = (int)($d->used_storage_mb ?? 0); -// -// // Defaults -// $this->quota_mb = $this->limit_default_quota_mb ?? 0; -// if (!$this->allow_rate_limit_override) { -// $this->rate_limit_per_hour = $this->limit_domain_rate_per_hour; -// $this->rate_limit_readonly = true; -// } else { -// $this->rate_limit_per_hour = $this->limit_domain_rate_per_hour; -// $this->rate_limit_readonly = false; -// } -// -// $this->rebuildEmailPreview(); -// $this->recomputeQuotaHints(); -// $this->recomputeBlockers(); -// } -// -// private function rebuildEmailPreview(): void -// { -// $this->email_preview = $this->localpart && $this->domain_name -// ? ($this->localpart . '@' . $this->domain_name) : ''; -// } -// -// private function recomputeQuotaHints(): void -// { -// $parts = []; -// -// if (!is_null($this->limit_total_quota_mb) && (int)$this->limit_total_quota_mb > 0) { -// $remainingNow = max(0, (int)$this->limit_total_quota_mb - (int)$this->domain_storage_used_mb); -// $remainingAfter = max(0, $remainingNow - max(0, (int)$this->quota_mb)); -// $parts[] = "Verbleibend jetzt: {$remainingNow} MiB"; -// $parts[] = "nach Speichern: {$remainingAfter} MiB"; -// } -// if (!is_null($this->limit_max_quota_per_mb)) $parts[] = "Max {$this->limit_max_quota_per_mb} MiB pro Postfach"; -// if (!is_null($this->limit_default_quota_mb)) $parts[] = "Standard: {$this->limit_default_quota_mb} MiB"; -// -// $this->quota_hint = implode(' · ', $parts); -// } -// -// private function recomputeBlockers(): void -// { -// // Slots -// $this->no_mailbox_slots = false; -// if (!is_null($this->limit_max_mailboxes)) { -// $free = (int)$this->limit_max_mailboxes - (int)$this->mailbox_count_used; -// if ($free <= 0) $this->no_mailbox_slots = true; -// } -// -// // Speicher -// $this->no_storage_left = false; -// if (!is_null($this->limit_total_quota_mb) && (int)$this->limit_total_quota_mb > 0) { -// $remaining = (int)$this->limit_total_quota_mb - (int)$this->domain_storage_used_mb; -// if ($remaining <= 0) $this->no_storage_left = true; -// } -// -// $reasons = []; -// if ($this->no_mailbox_slots) $reasons[] = 'Keine freien Postfach-Slots in dieser Domain.'; -// if ($this->no_storage_left) $reasons[] = 'Kein Domain-Speicher mehr verfügbar.'; -// $this->block_reason = implode(' ', $reasons); -// $this->can_create = !($this->no_mailbox_slots || $this->no_storage_left); -// } -// -// /* ---------- Save ---------- */ -// #[On('mailbox:create')] -// public function save(): void -// { -// $this->recomputeBlockers(); -// if (!$this->can_create) { -// $this->addError('domain_id', $this->block_reason ?: 'Erstellung aktuell nicht möglich.'); -// return; -// } -// -// $data = $this->validate(); -// $email = $data['localpart'] . '@' . $this->domain_name; -// -// try { -// $u = new MailUser(); -// $u->domain_id = $data['domain_id']; -// $u->localpart = $data['localpart']; -// $u->email = $email; -// $u->display_name = $this->display_name ?: null; -// $u->password_hash = $this->password ? Hash::make($this->password) : null; -// $u->is_system = false; -// $u->is_active = (bool)$data['is_active']; -// $u->must_change_pw = (bool)$data['must_change_pw']; -// $u->quota_mb = (int)$data['quota_mb']; -// $u->rate_limit_per_hour = $data['rate_limit_per_hour']; -// $u->save(); -// } catch (QueryException $e) { -// $msg = strtolower($e->getMessage()); -// if (str_contains($msg, 'mail_users_domain_localpart_unique')) { -// $this->addError('localpart', 'Dieses Postfach existiert in dieser Domain bereits.'); -// return; -// } -// if (str_contains($msg, 'mail_users_email_unique')) { -// $this->addError('localpart', 'Diese E-Mail-Adresse ist bereits vergeben.'); -// return; -// } -// throw $e; -// } -// -// $this->dispatch('mailbox:created'); -// $this->dispatch('closeModal'); -// $this->dispatch('toast', -// type: 'done', -// badge: 'Postfach', -// title: 'Postfach angelegt', -// text: 'Das Postfach ' . e($email) . ' wurde erfolgreich angelegt.', -// duration: 6000 -// ); -// -// } -// -// public static function modalMaxWidth(): string -// { -// return '3xl'; -// } -// -// public function render() -// { -// return view('livewire.ui.mail.modal.mailbox-create-modal'); -// } -//} diff --git a/app/Livewire/Ui/System/Form/DomainsSslForm.php b/app/Livewire/Ui/System/Form/DomainsSslForm.php new file mode 100644 index 0000000..a5b8582 --- /dev/null +++ b/app/Livewire/Ui/System/Form/DomainsSslForm.php @@ -0,0 +1,49 @@ + 'nullable|string|max:190', + 'webmail_domain' => 'nullable|string|max:190', + ]; + } + + public function mount(): void + { + $this->mail_domain_readonly = (string) config('mailwolt.domain.mail', 'mx'); + $this->ui_domain = Setting::get('ui_domain', $this->ui_domain); + $this->webmail_domain = Setting::get('webmail_domain', $this->webmail_domain); + } + + public function save(): void + { + $this->validate(); + + Setting::put('ui_domain', $this->ui_domain); + Setting::put('webmail_domain', $this->webmail_domain); + + $this->dispatch('toast', + type: 'done', + badge: 'System', + title: 'Domains gespeichert', + text: 'UI- und Webmail-Domain wurden übernommen.', + duration: 5000, + ); + } + + public function render() { return view('livewire.ui.system.form.domains-ssl-form'); } +} diff --git a/app/Livewire/Ui/System/Form/GeneralForm.php b/app/Livewire/Ui/System/Form/GeneralForm.php new file mode 100644 index 0000000..8dbd12a --- /dev/null +++ b/app/Livewire/Ui/System/Form/GeneralForm.php @@ -0,0 +1,79 @@ + 'required|string|max:10', + 'timezone' => 'required|string|max:64', + ]; + } + + public function mount(): void + { + // Defaults aus ENV nur für den allerersten Seed in Settings (Redis/DB) + $envLocale = env('APP_LOCALE') ?? env('APP_FALLBACK_LOCALE') ?? $this->locale; + $envTimezone = env('APP_TIMEZONE') ?? $this->timezone; + + // Wenn (noch) nichts in Settings liegt, einmalig mit ENV-Werten befüllen + if (Setting::get('locale', null) === null) { + Setting::set('locale', $envLocale); + } + if (Setting::get('timezone', null) === null) { + Setting::set('timezone', $envTimezone); + } + + // Ab hier ausschließlich aus Settings lesen (Redis → DB Fallback) + $this->locale = (string) Setting::get('locale', $envLocale); + $this->timezone = (string) Setting::get('timezone', $envTimezone); + + // Sofort für die aktuelle Request anwenden + app()->setLocale($this->locale); + @date_default_timezone_set($this->timezone); + config([ + 'app.locale' => $this->locale, + 'app.fallback_locale' => $this->locale, + 'app.timezone' => $this->timezone, + ]); + } + + public function save(): void + { + $this->validate(); + + // Persistieren: DB → Redis (siehe Setting::set) + Setting::set('locale', $this->locale); + Setting::set('timezone', $this->timezone); + + // Direkt in der laufenden Request aktivieren + app()->setLocale($this->locale); + @date_default_timezone_set($this->timezone); + config([ + 'app.locale' => $this->locale, + 'app.fallback_locale' => $this->locale, // optional + 'app.timezone' => $this->timezone, + ]); + + $this->dispatch('toast', + type: 'done', + badge: 'System', + title: 'Allgemein gespeichert', + text: 'Sprache und Zeitzone wurden übernommen.', + duration: 5000, + ); + } + + public function render() + { + return view('livewire.ui.system.form.general-form'); + } +} diff --git a/app/Livewire/Ui/System/Form/SecurityForm.php b/app/Livewire/Ui/System/Form/SecurityForm.php new file mode 100644 index 0000000..2c18053 --- /dev/null +++ b/app/Livewire/Ui/System/Form/SecurityForm.php @@ -0,0 +1,48 @@ + 'boolean', + 'rate_limit' => 'nullable|integer|min:1|max:100', + 'password_min' => 'nullable|integer|min:6|max:128', + ]; + } + + public function mount(): void + { + $this->twofa_enabled = (bool) Setting::get('twofa_enabled', $this->twofa_enabled); + $this->rate_limit = (int) Setting::get('rate_limit', $this->rate_limit); + $this->password_min = (int) Setting::get('password_min', $this->password_min); + } + + public function save(): void + { + $this->validate(); + + Setting::put('twofa_enabled', $this->twofa_enabled); + Setting::put('rate_limit', $this->rate_limit); + Setting::put('password_min', $this->password_min); + + $this->dispatch('toast', + type: 'done', + badge: 'Sicherheit', + title: 'Sicherheit gespeichert', + text: '2FA/Rate-Limits/Passwortregeln wurden übernommen.', + duration: 5000, + ); + } + + public function render() { return view('livewire.ui.system.form.security-form'); } +} diff --git a/config/mailwolt.php b/config/mailwolt.php index e04e4c3..22886b0 100644 --- a/config/mailwolt.php +++ b/config/mailwolt.php @@ -1,6 +1,30 @@ [ + 'base' => env('BASE_DOMAIN'), + 'mail' => env('MTA_SUB'), + 'ui' => env('UI_SUB'), + 'webmail' => env('WEBMAIL_SUB'), + ], + + 'language' => [ + + 'de' => [ + 'label' => 'Deutsch', + 'locale' => 'de', + 'fallback_locale' => 'de', + 'flag' => 'de', + ], + + 'en' => [ + 'label' => 'English', + 'locale' => 'en', + 'fallback_locale' => 'en', + 'flag' => 'gb', + ], + ], + 'units' => [ ['name' => 'nginx', 'action' => 'reload'], ['name' => 'postfix', 'action' => 'try-reload-or-restart'], diff --git a/config/menu/sidebar.php b/config/menu/sidebar.php index 514e7c7..7130057 100644 --- a/config/menu/sidebar.php +++ b/config/menu/sidebar.php @@ -10,8 +10,8 @@ return [ 'label' => 'Mail', 'icon' => 'ph-envelope', 'items' => [ ['label' => 'Postfächer', 'route' => 'ui.mail.mailboxes.index'], ['label' => 'Aliasse', 'route' => 'ui.mail.aliases.index'], - ['label' => 'Gruppen', 'route' => 'ui.mail.groups.index'], - ['label' => 'Filter', 'route' => 'ui.mail.filters.index'], +// ['label' => 'Gruppen', 'route' => 'ui.mail.groups.index'], +// ['label' => 'Filter', 'route' => 'ui.mail.filters.index'], ['label' => 'Quarantäne', 'route' => 'ui.mail.quarantine.index'], ['label' => 'Queues', 'route' => 'ui.mail.queues.index'], ], @@ -19,45 +19,59 @@ return [ [ 'label' => 'Domains', 'icon' => 'ph-globe', 'items' => [ ['label' => 'Übersicht', 'route' => 'ui.domains.index'], - ['label' => 'DNS-Assistent', 'route' => 'ui.domains.dns'], - ['label' => 'Zertifikate', 'route' => 'ui.domains.certificates'], +// ['label' => 'DNS-Assistent', 'route' => 'ui.domains.dns'], +// ['label' => 'Zertifikate', 'route' => 'ui.domains.certificates'], ], ], [ 'label' => 'Webmail', 'icon' => 'ph-browser', 'items' => [ - ['label' => 'Allgemein', 'route' => 'ui.webmail.settings'], - ['label' => 'Plugins', 'route' => 'ui.webmail.plugins'], + ['label' => 'Allgemein', 'route' => 'ui.logout'], + ['label' => 'Plugins', 'route' => 'ui.logout'], +// ['label' => 'Allgemein', 'route' => 'ui.webmail.settings'], +// ['label' => 'Plugins', 'route' => 'ui.webmail.plugins'], ], ], [ 'label' => 'Benutzer', 'icon' => 'ph-users', 'items' => [ - ['label' => 'Benutzer', 'route' => 'ui.users.index'], - ['label' => 'Rollen & Rechte', 'route' => 'ui.users.roles'], - ['label' => 'Anmeldesicherheit', 'route' => 'ui.users.security'], + ['label' => 'Benutzer', 'route' => 'ui.logout'], + ['label' => 'Rollen & Rechte', 'route' => 'ui.logout'], + ['label' => 'Anmeldesicherheit', 'route' => 'ui.logout'], +// ['label' => 'Benutzer', 'route' => 'ui.users.index'], +// ['label' => 'Rollen & Rechte', 'route' => 'ui.users.roles'], +// ['label' => 'Anmeldesicherheit', 'route' => 'ui.users.security'], ], ], [ 'label' => 'Sicherheit', 'icon' => 'ph-shield', 'items' => [ ['label' => 'TLS & Ciphers', 'route' => 'ui.security.tls'], - ['label' => 'Ratelimits', 'route' => 'ui.security.abuse'], + ['label' => 'Fail2Ban', 'route' => 'ui.security.fail2ban'], ['label' => 'Audit-Logs', 'route' => 'ui.security.audit'], + ['label' => 'Rspamd', 'route' => 'ui.security.rspamd'], + ['label' => 'SSL', 'route' => 'ui.security.ssl'], +// ['label' => 'Ratelimits', 'route' => 'ui.security.audit'], +// ['label' => 'TLS & Ciphers', 'route' => 'ui.security.tls'], +// ['label' => 'Ratelimits', 'route' => 'ui.security.abuse'], +// ['label' => 'Audit-Logs', 'route' => 'ui.security.audit'], ], ], [ 'label' => 'System', 'icon' => 'ph-gear-six', 'items' => [ ['label' => 'Einstellungen', 'route' => 'ui.system.settings'], - ['label' => 'Dienste & Status', 'route' => 'ui.system.services'], - ['label' => 'Jobs & Queues', 'route' => 'ui.system.jobs'], - ['label' => 'Logs', 'route' => 'ui.system.logs'], - ['label' => 'Speicher', 'route' => 'ui.system.storage'], - ['label' => 'Über', 'route' => 'ui.system.about'], +// ['label' => 'Dienste & Status', 'route' => 'ui.system.services'], +// ['label' => 'Jobs & Queues', 'route' => 'ui.system.jobs'], +// ['label' => 'Logs', 'route' => 'ui.system.logs'], +// ['label' => 'Speicher', 'route' => 'ui.system.storage'], +// ['label' => 'Über', 'route' => 'ui.system.about'], ], ], [ 'label' => 'Entwickler', 'icon' => 'ph-brackets-curly', 'items' => [ - ['label' => 'API-Schlüssel', 'route' => 'ui.dev.tokens'], - ['label' => 'Webhooks', 'route' => 'ui.dev.webhooks'], - ['label' => 'Sandbox', 'route' => 'ui.dev.sandbox'], + ['label' => 'API-Schlüssel', 'route' => 'ui.logout'], + ['label' => 'Webhooks', 'route' => 'ui.logout'], + ['label' => 'Sandbox', 'route' => 'ui.logout'], +// ['label' => 'API-Schlüssel', 'route' => 'ui.dev.tokens'], +// ['label' => 'Webhooks', 'route' => 'ui.dev.webhooks'], +// ['label' => 'Sandbox', 'route' => 'ui.dev.sandbox'], ], ], ]; diff --git a/resources/views/components/partials/sidebar.blade.php b/resources/views/components/partials/sidebar.blade.php index e9a7c3e..f69695c 100644 --- a/resources/views/components/partials/sidebar.blade.php +++ b/resources/views/components/partials/sidebar.blade.php @@ -62,8 +62,8 @@ $itemIcon = $item['icon'] ?? 'ph-dot-outline'; @endphp
  • - {{-- - {{-- DOMAIN (TailwindPlus Elements) --}}
    diff --git a/resources/views/livewire/ui/system/form/domains-ssl-form.blade.php b/resources/views/livewire/ui/system/form/domains-ssl-form.blade.php new file mode 100644 index 0000000..99bb6c2 --- /dev/null +++ b/resources/views/livewire/ui/system/form/domains-ssl-form.blade.php @@ -0,0 +1,33 @@ +
    +
    + + +

    Wird aus ENV/Config gelesen und ist nicht änderbar.

    +
    + +
    + + + @error('ui_domain')

    {{ $message }}

    @enderror +
    + +
    + + + @error('webmail_domain')

    {{ $message }}

    @enderror +
    + +
    + +
    + +
    + TLS/Redirect ist systemweit immer erzwungen (HTTPS). ACME/Zertifikate haben ihren eigenen Reiter. +
    +
    diff --git a/resources/views/livewire/ui/system/form/general-form.blade.php b/resources/views/livewire/ui/system/form/general-form.blade.php new file mode 100644 index 0000000..c806eb5 --- /dev/null +++ b/resources/views/livewire/ui/system/form/general-form.blade.php @@ -0,0 +1,35 @@ +
    + {{-- Sprache --}} +
    + + + @error('locale')

    {{ $message }}

    @enderror +
    + + {{-- Zeitzone --}} +
    + + + @error('timezone')

    {{ $message }}

    @enderror +
    + + {{-- Actions: immer unten rechts, volle Breite, rechts ausgerichtet --}} +
    + +
    +
    diff --git a/resources/views/livewire/ui/system/form/security-form.blade.php b/resources/views/livewire/ui/system/form/security-form.blade.php new file mode 100644 index 0000000..a9dbeef --- /dev/null +++ b/resources/views/livewire/ui/system/form/security-form.blade.php @@ -0,0 +1,27 @@ +
    + + +
    + + + @error('rate_limit')

    {{ $message }}

    @enderror +
    + +
    + + + @error('password_min')

    {{ $message }}

    @enderror +
    + +
    + +
    +
    diff --git a/resources/views/ui/system/settings.blade.php b/resources/views/ui/system/settings.blade.php index 7576a50..ca52987 100644 --- a/resources/views/ui/system/settings.blade.php +++ b/resources/views/ui/system/settings.blade.php @@ -1,14 +1,3 @@ -{{-- resources/views/ui/system/settings.blade.php --}} -{{--@extends('layouts.app')--}} - -{{--@section('title', 'System · Einstellungen')--}} -{{--@section('header_title', 'System · Einstellungen')--}} - -{{--@section('content')--}} -{{--
    --}} -{{-- --}} -{{--
    --}} -{{--@endsection--}} {{-- resources/views/ui/system/settings/index.blade.php --}} @extends('layouts.app') @@ -55,7 +44,7 @@
    {{-- Livewire-Form (Allgemein) --}} - + diff --git a/routes/web.php b/routes/web.php index bf289ee..6b306d4 100644 --- a/routes/web.php +++ b/routes/web.php @@ -37,14 +37,16 @@ Route::middleware('auth.user')->name('ui.')->group(function () { }); #DOMAIN ROUTES - Route::name('domain.')->group(function () { + Route::name('domains.')->group(function () { Route::get('/domains', [DomainDnsController::class, 'index'])->name('index'); }); #MAIL ROUTES Route::name('mail.')->group(function () { - Route::get('/mailboxes', [MailboxController::class, 'index'])->name('mailbox.index'); + Route::get('/mailboxes', [MailboxController::class, 'index'])->name('mailboxes.index'); Route::get('/aliases', [AliasController::class, 'index'])->name('aliases.index'); + Route::get('/quarantine', function () {return 'Quarantäne';})->name('quarantine.index'); + Route::get('/queues', function () {return 'Queues';})->name('queues.index'); }); #LOGOUT ROUTE