299 lines
10 KiB
PHP
299 lines
10 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Ui\Webmail;
|
|
|
|
use App\Models\MailUser;
|
|
use App\Services\ImapService;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Livewire\Attributes\Layout;
|
|
use Livewire\Attributes\Title;
|
|
use Livewire\Attributes\Url;
|
|
use Livewire\Component;
|
|
use Livewire\WithFileUploads;
|
|
|
|
#[Layout('layouts.webmail')]
|
|
#[Title('Schreiben · Webmail')]
|
|
class Compose extends Component
|
|
{
|
|
use WithFileUploads;
|
|
|
|
public string $to = '';
|
|
public string $subject = '';
|
|
public string $body = '';
|
|
public bool $sent = false;
|
|
|
|
// Livewire temp upload binding — cleared immediately after moving to stagedAttachments
|
|
public array $attachments = [];
|
|
|
|
// Persistent staged attachments: [{name, size, mime, path}] — survives reloads via session
|
|
public array $stagedAttachments = [];
|
|
|
|
// Signature — loaded from DB, kept separate from body
|
|
public string $signatureRaw = '';
|
|
public bool $signatureIsHtml = false;
|
|
|
|
// Reply mode
|
|
#[Url] public int $replyUid = 0;
|
|
#[Url] public string $replyFolder = 'INBOX';
|
|
public bool $isReply = false;
|
|
public string $replyMeta = '';
|
|
|
|
// Draft editing
|
|
#[Url] public int $draftUid = 0;
|
|
#[Url] public string $draftFolder = 'Drafts';
|
|
public bool $isDraft = false;
|
|
|
|
public function mount(): void
|
|
{
|
|
if (! session('webmail_email')) {
|
|
$this->redirect(route('ui.webmail.login'));
|
|
return;
|
|
}
|
|
|
|
// Restore staged attachments from session, drop any whose files were cleaned up
|
|
$staged = session('compose_attachments', []);
|
|
$this->stagedAttachments = array_values(
|
|
array_filter($staged, fn($a) => Storage::exists($a['path']))
|
|
);
|
|
|
|
$user = MailUser::where('email', session('webmail_email'))->first();
|
|
$prefs = $user?->webmail_prefs ?? [];
|
|
$sig = (string) ($user?->signature ?? '');
|
|
|
|
if ($this->draftUid > 0) {
|
|
$this->loadDraft();
|
|
return;
|
|
}
|
|
|
|
// Signatur laden — wird NICHT in den Body injiziert, sondern separat angezeigt
|
|
if ($sig) {
|
|
$this->signatureIsHtml = (bool) preg_match('/<[a-zA-Z][^>]*>/', $sig);
|
|
$applyNew = ($prefs['signature_new'] ?? true);
|
|
$applyReply = ($prefs['signature_reply'] ?? false);
|
|
|
|
if ($this->replyUid > 0 && $applyReply) {
|
|
$this->signatureRaw = $sig;
|
|
} elseif ($this->replyUid === 0 && $applyNew) {
|
|
$this->signatureRaw = $sig;
|
|
}
|
|
}
|
|
|
|
if ($this->replyUid > 0) {
|
|
$this->loadReply();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Called by Livewire when $attachments changes (after upload completes)
|
|
public function updatedAttachments(): void
|
|
{
|
|
$dir = 'webmail-tmp/' . session()->getId();
|
|
|
|
foreach ($this->attachments as $file) {
|
|
$filename = uniqid('att_') . '_' . preg_replace('/[^a-zA-Z0-9._-]/', '_', $file->getClientOriginalName());
|
|
$path = $file->storeAs($dir, $filename, 'local');
|
|
|
|
$this->stagedAttachments[] = [
|
|
'name' => $file->getClientOriginalName(),
|
|
'size' => $file->getSize(),
|
|
'mime' => $file->getMimeType() ?: 'application/octet-stream',
|
|
'path' => $path,
|
|
];
|
|
}
|
|
|
|
$this->attachments = [];
|
|
session(['compose_attachments' => $this->stagedAttachments]);
|
|
}
|
|
|
|
private function loadDraft(): void
|
|
{
|
|
$this->isDraft = true;
|
|
try {
|
|
$imap = app(ImapService::class);
|
|
$client = $imap->client(session('webmail_email'), session('webmail_password'));
|
|
$orig = $imap->getMessage($client, $this->draftFolder, $this->draftUid);
|
|
$client->disconnect();
|
|
|
|
$this->to = $orig['to'] ?? '';
|
|
$this->subject = $orig['subject'] ?? '';
|
|
$this->body = $orig['text'] ?? strip_tags($orig['html'] ?? '');
|
|
} catch (\Throwable) {}
|
|
}
|
|
|
|
private function loadReply(): void
|
|
{
|
|
$this->isReply = true;
|
|
try {
|
|
$imap = app(ImapService::class);
|
|
$client = $imap->client(session('webmail_email'), session('webmail_password'));
|
|
$orig = $imap->getMessage($client, $this->replyFolder, $this->replyUid);
|
|
$client->disconnect();
|
|
|
|
$this->to = $orig['from'] ?? '';
|
|
|
|
$sub = $orig['subject'] ?? '';
|
|
$this->subject = str_starts_with(strtolower($sub), 're:') ? $sub : 'Re: ' . $sub;
|
|
|
|
$date = $orig['date']
|
|
? \Carbon\Carbon::parse($orig['date'])->format('d.m.Y \u\m H:i \U\h\r')
|
|
: '';
|
|
$fromName = $orig['from_name'] ?: ($orig['from'] ?? '');
|
|
$fromAddr = $orig['from'] ?? '';
|
|
$this->replyMeta = "Am {$date} schrieb {$fromName} <{$fromAddr}>:";
|
|
|
|
$quoteText = $orig['text'] ?: strip_tags($orig['html'] ?? '');
|
|
$quoted = implode("\n", array_map(
|
|
fn($l) => '> ' . $l,
|
|
explode("\n", rtrim($quoteText))
|
|
));
|
|
|
|
$this->body = "\n\n\n" . $this->replyMeta . "\n" . $quoted;
|
|
} catch (\Throwable) {}
|
|
}
|
|
|
|
public function back(): void
|
|
{
|
|
$hasContent = trim($this->to)
|
|
|| trim($this->subject)
|
|
|| trim(preg_replace('/^[\s>]+$/m', '', $this->body));
|
|
|
|
if ($hasContent) {
|
|
$this->saveCurrentDraft(true);
|
|
}
|
|
|
|
$this->clearStagedAttachments();
|
|
$this->redirect(route('ui.webmail.inbox'));
|
|
}
|
|
|
|
public function send(): void
|
|
{
|
|
$this->validate([
|
|
'to' => 'required|email',
|
|
'subject' => 'required|string|max:255',
|
|
'body' => 'required|string',
|
|
]);
|
|
|
|
try {
|
|
$imap = app(ImapService::class);
|
|
if ($this->signatureIsHtml && $this->signatureRaw) {
|
|
$htmlBody = '<div style="font-family:inherit;font-size:13px;line-height:1.6;white-space:pre-wrap;">'
|
|
. htmlspecialchars($this->body)
|
|
. '</div>'
|
|
. '<br><div style="border-top:1px solid #e5e7eb;margin-top:12px;padding-top:10px;font-size:12.5px;">'
|
|
. $this->signatureRaw
|
|
. '</div>';
|
|
$imap->sendMessage(
|
|
session('webmail_email'),
|
|
session('webmail_password'),
|
|
$this->to,
|
|
$this->subject,
|
|
$htmlBody,
|
|
$this->stagedAttachments,
|
|
true,
|
|
);
|
|
} else {
|
|
$plainBody = $this->body;
|
|
if ($this->signatureRaw) {
|
|
$plainBody .= "\n\n" . $this->normalizeSignature($this->signatureRaw);
|
|
}
|
|
$imap->sendMessage(
|
|
session('webmail_email'),
|
|
session('webmail_password'),
|
|
$this->to,
|
|
$this->subject,
|
|
$plainBody,
|
|
$this->stagedAttachments,
|
|
);
|
|
}
|
|
|
|
if ($this->draftUid > 0) {
|
|
try {
|
|
$client = $imap->client(session('webmail_email'), session('webmail_password'));
|
|
$imap->deleteMessage($client, $this->draftFolder, $this->draftUid);
|
|
$client->disconnect();
|
|
} catch (\Throwable) {}
|
|
}
|
|
|
|
$this->clearStagedAttachments();
|
|
|
|
$this->sent = true;
|
|
$this->to = '';
|
|
$this->subject = '';
|
|
$this->body = '';
|
|
$this->isReply = false;
|
|
$this->isDraft = false;
|
|
|
|
$this->dispatch('toast', type: 'done', badge: 'Webmail',
|
|
title: 'Gesendet', text: 'Nachricht wurde erfolgreich gesendet.', duration: 4000);
|
|
} catch (\Throwable $e) {
|
|
$this->addError('to', 'Senden fehlgeschlagen: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
public function removeAttachment(int $index): void
|
|
{
|
|
if (isset($this->stagedAttachments[$index])) {
|
|
Storage::delete($this->stagedAttachments[$index]['path']);
|
|
}
|
|
array_splice($this->stagedAttachments, $index, 1);
|
|
session(['compose_attachments' => $this->stagedAttachments]);
|
|
}
|
|
|
|
private function normalizeSignature(string $sig): string
|
|
{
|
|
$trimmed = ltrim($sig, "\r\n");
|
|
// Prüfen ob der User bereits "-- " als Trenner eingetragen hat
|
|
if (str_starts_with($trimmed, '-- ') || str_starts_with($trimmed, "--\n") || str_starts_with($trimmed, "--\r\n")) {
|
|
return $trimmed;
|
|
}
|
|
return "-- \n" . $trimmed;
|
|
}
|
|
|
|
private function clearStagedAttachments(): void
|
|
{
|
|
foreach ($this->stagedAttachments as $att) {
|
|
Storage::delete($att['path']);
|
|
}
|
|
$this->stagedAttachments = [];
|
|
session()->forget('compose_attachments');
|
|
}
|
|
|
|
private function saveCurrentDraft(bool $notify = false): void
|
|
{
|
|
try {
|
|
$imap = app(ImapService::class);
|
|
$client = $imap->client(session('webmail_email'), session('webmail_password'));
|
|
|
|
if ($this->draftUid > 0) {
|
|
$imap->deleteMessage($client, $this->draftFolder, $this->draftUid);
|
|
}
|
|
|
|
$imap->saveDraft($client, session('webmail_email'), $this->to, $this->subject, $this->body);
|
|
$client->disconnect();
|
|
|
|
if ($notify) {
|
|
$this->dispatch('toast', type: 'done', badge: 'Webmail',
|
|
title: 'Entwurf gespeichert', text: 'Nachricht in Entwürfe gespeichert.', duration: 3000);
|
|
}
|
|
} catch (\Throwable $e) {
|
|
\Illuminate\Support\Facades\Log::warning('Draft save failed', ['error' => $e->getMessage()]);
|
|
}
|
|
}
|
|
|
|
public function autoSave(): void
|
|
{
|
|
if ($this->sent) return;
|
|
$hasContent = trim($this->to)
|
|
|| trim($this->subject)
|
|
|| trim(preg_replace('/^[\s>]+$/m', '', $this->body));
|
|
if (! $hasContent) return;
|
|
|
|
$this->saveCurrentDraft(false);
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
return view('livewire.ui.webmail.compose');
|
|
}
|
|
}
|