mailwolt/app/Services/ImapService.php

296 lines
11 KiB
PHP

<?php
namespace App\Services;
use Webklex\PHPIMAP\ClientManager;
use Webklex\PHPIMAP\Client;
use Webklex\PHPIMAP\Folder;
class ImapService
{
private ClientManager $manager;
public function __construct()
{
$this->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;
}
}