manager = new ClientManager(config('imap')); } public function client(string $email, string $password): Client { $client = $this->manager->make([ 'host' => config('imap.accounts.webmail.host', '127.0.0.1'), 'port' => config('imap.accounts.webmail.port', 143), 'protocol' => 'imap', 'encryption' => config('imap.accounts.webmail.encryption', 'notls'), 'validate_cert' => false, 'username' => $email, 'password' => $password, ]); $client->connect(); return $client; } public function folders(Client $client): array { $order = ['INBOX' => 0, 'Drafts' => 1, 'Sent' => 2, 'Junk' => 3, 'Trash' => 4, 'Archive' => 5]; $folders = []; foreach ($client->getFolders(false) as $folder) { $folders[] = [ 'name' => $folder->name, 'full_name' => $folder->full_name, 'path' => $folder->path, ]; } usort($folders, fn ($a, $b) => ($order[$a['name']] ?? 99) <=> ($order[$b['name']] ?? 99) ); return $folders; } public function starredMessages(Client $client, int $page = 1, int $perPage = 25): array { $rows = []; foreach ($client->getFolders(false) as $folder) { if (in_array($folder->name, ['Trash', 'Spam', 'Junk'])) continue; try { $msgs = $folder->query()->where('FLAGGED')->setFetchFlags(true)->setFetchBody(false)->get(); foreach ($msgs as $msg) { $toAttr = $msg->getTo(); $toAddr = ''; foreach ($toAttr ?? [] as $r) { $toAddr = (string)($r->mail ?? ''); break; } $rows[] = [ 'uid' => $msg->getUid(), 'folder' => $folder->path, 'subject' => (string) $msg->getSubject(), 'from' => (string) ($msg->getFrom()[0]->mail ?? ''), 'from_name' => (string) ($msg->getFrom()[0]->personal ?? ''), 'to' => $toAddr, 'date' => $msg->getDate()?->toDate()?->toDateTimeString(), 'seen' => $msg->getFlags()->has('seen'), 'flagged' => true, 'has_attachments' => count($msg->getAttachments()) > 0, ]; } } catch (\Throwable) {} } usort($rows, fn($a, $b) => strcmp($b['date'] ?? '', $a['date'] ?? '')); $total = count($rows); $paged = array_slice($rows, ($page - 1) * $perPage, $perPage); return ['messages' => $paged, 'total' => $total, 'page' => $page, 'perPage' => $perPage]; } public function messages(Client $client, string $folderPath = 'INBOX', int $page = 1, int $perPage = 25): array { $folder = $client->getFolder($folderPath); $query = $folder->query()->all(); $total = $query->count(); $messages = $query ->setFetchFlags(true) ->setFetchBody(false) ->limit($perPage, ($page - 1) * $perPage) ->get(); $rows = []; foreach ($messages as $msg) { $toAttr = $msg->getTo(); $toAddr = ''; foreach ($toAttr ?? [] as $r) { $toAddr = (string)($r->mail ?? ''); break; } $rows[] = [ 'uid' => $msg->getUid(), 'subject' => (string) $msg->getSubject(), 'from' => (string) ($msg->getFrom()[0]->mail ?? ''), 'from_name' => (string) ($msg->getFrom()[0]->personal ?? ''), 'to' => $toAddr, 'date' => $msg->getDate()?->toDate()?->toDateTimeString(), 'seen' => $msg->getFlags()->has('seen'), 'flagged' => $msg->getFlags()->has('flagged'), 'has_attachments' => count($msg->getAttachments()) > 0, ]; } return [ 'messages' => array_reverse($rows), 'total' => $total, 'page' => $page, 'perPage' => $perPage, ]; } public function getMessage(Client $client, string $folderPath, int $uid): array { $folder = $client->getFolder($folderPath); $message = $folder->query()->getMessageByUid($uid); if (! $message) { return []; } $html = (string) $message->getHtmlBody(); $text = (string) $message->getTextBody(); $froms = $message->getFrom(); $from = $froms[0] ?? null; $to = []; foreach ($message->getTo() ?? [] as $r) { $to[] = (string) ($r->mail ?? ''); } $attachments = []; foreach ($message->getAttachments() as $att) { $attachments[] = [ 'name' => $att->getName(), 'type' => $att->getMimeType(), 'size' => $att->getSize(), ]; } return [ 'uid' => $message->getUid(), 'subject' => (string) $message->getSubject(), 'from' => (string) ($from->mail ?? ''), 'from_name' => (string) ($from->personal ?? ''), 'to' => implode(', ', $to), 'date' => $message->getDate()?->toDate()?->toDateTimeString(), 'html' => $html, 'text' => $text, 'attachments' => $attachments, 'seen' => $message->getFlags()->has('seen'), 'flagged' => $message->getFlags()->has('flagged'), ]; } public function sendMessage(string $from, string $password, string $to, string $subject, string $body, array $attachments = [], bool $isHtml = false): void { $attachFn = function ($msg) use ($attachments) { foreach ($attachments as $att) { $diskPath = \Illuminate\Support\Facades\Storage::path($att['path']); if (! file_exists($diskPath)) continue; $msg->attachData( file_get_contents($diskPath), $att['name'], ['mime' => $att['mime'] ?: 'application/octet-stream'], ); } }; if ($isHtml) { \Illuminate\Support\Facades\Mail::html($body, function ($msg) use ($from, $to, $subject, $attachFn) { $msg->from($from)->to($to)->subject($subject); $attachFn($msg); }); } else { \Illuminate\Support\Facades\Mail::raw($body, function ($msg) use ($from, $to, $subject, $attachFn) { $msg->from($from)->to($to)->subject($subject); $attachFn($msg); }); } } public function saveDraft(Client $client, string $from, string $to, string $subject, string $body): void { $date = now()->format('D, d M Y H:i:s O'); $raw = "Date: {$date}\r\n" . "From: {$from}\r\n" . "To: {$to}\r\n" . "Subject: {$subject}\r\n" . "MIME-Version: 1.0\r\n" . "Content-Type: text/plain; charset=UTF-8\r\n" . "Content-Transfer-Encoding: 8bit\r\n" . "\r\n" . $body; $folder = $client->getFolder('Drafts'); $folder->appendMessage($raw, ['\\Draft', '\\Seen'], now()); } public function moveMessage(Client $client, string $fromFolder, int $uid, string $toFolder): void { $folder = $client->getFolder($fromFolder); $message = $folder->query()->getMessageByUid($uid); $message?->move($toFolder, true); } public function toggleFlag(Client $client, string $folderPath, int $uid): bool { $folder = $client->getFolder($folderPath); $message = $folder->query()->getMessageByUid($uid); if (! $message) return false; $flagged = $message->getFlags()->has('flagged'); if ($flagged) { $message->unsetFlag('Flagged'); return false; } $message->setFlag('Flagged'); return true; } public function deleteMessage(Client $client, string $folderPath, int $uid): void { $folder = $client->getFolder($folderPath); $message = $folder->query()->getMessageByUid($uid); $message?->delete(true); } public function markSeen(Client $client, string $folderPath, int $uid): void { $folder = $client->getFolder($folderPath); $message = $folder->query()->getMessageByUid($uid); $message?->setFlag('Seen'); } public function markUnseen(Client $client, string $folderPath, int $uid): void { $folder = $client->getFolder($folderPath); $message = $folder->query()->getMessageByUid($uid); $message?->unsetFlag('Seen'); } public function searchMessages(Client $client, string $folderPath, string $query, int $limit = 15): array { $folder = $client->getFolder($folderPath); $results = []; $seen = []; foreach (['SUBJECT', 'FROM'] as $criterion) { try { $msgs = $folder->query() ->where($criterion, $query) ->setFetchFlags(true) ->setFetchBody(false) ->get(); foreach ($msgs as $msg) { $uid = $msg->getUid(); if (isset($seen[$uid])) continue; $seen[$uid] = true; $froms = $msg->getFrom(); $from = $froms[0] ?? null; $to = []; foreach ($msg->getTo() ?? [] as $r) { $to[] = (string)($r->mail ?? ''); } $results[] = [ 'uid' => $uid, 'subject' => (string) $msg->getSubject(), 'from' => (string) ($from->mail ?? ''), 'from_name' => (string) ($from->personal ?? ''), 'to' => implode(', ', $to), 'date' => $msg->getDate()?->toDate()?->toDateTimeString(), 'seen' => $msg->getFlags()->has('seen'), 'flagged' => $msg->getFlags()->has('flagged'), ]; if (count($results) >= $limit) break 2; } } catch (\Throwable) {} } return $results; } }