clientManager = new ClientManager(); } public function buildClient(ImapAccount $account) { return $this->makeClientFromConfig([ 'host' => $account->imap_host, 'port' => $account->imap_port, 'encryption' => $account->imap_encryption === 'none' ? false : $account->imap_encryption, 'validate_cert' => $account->validate_cert ?? false, 'username' => $account->username, 'password' => $account->password, 'protocol' => 'imap', 'timeout' => $this->connectTimeout, ]); } protected function makeClientFromConfig(array $config) { $originalSocketTimeout = ini_get('default_socket_timeout'); ini_set('default_socket_timeout', $this->connectTimeout); $client = $this->clientManager->make($config); ini_set('default_socket_timeout', $originalSocketTimeout); if (!($config['validate_cert'] ?? false)) { $client->setOptions([ 'ssl' => [ 'verify_peer' => false, 'verify_peer_name' => false, 'allow_self_signed' => true, ], ]); } return $client; } public function testConnection(array $config): void { set_time_limit(max(30, $this->connectTimeout + 15)); $client = $this->makeClientFromConfig([ 'host' => $config['imap_host'], 'port' => $config['imap_port'], 'encryption' => $config['imap_encryption'] === 'none' ? false : $config['imap_encryption'], 'validate_cert' => $config['validate_cert'] ?? false, 'username' => $config['username'], 'password' => $config['password'], 'protocol' => 'imap', 'timeout' => $this->connectTimeout, ]); $client->connect(); $client->disconnect(); } public function syncAccount(ImapAccount $account): array { set_time_limit(180); // Get the highest UID already imported for this account $maxUid = EmailMessage::where('imap_account_id', $account->id)->max('uid') ?? 0; $client = $this->buildClient($account); try { $client->connect(); } catch (Exception $e) { $account->update([ 'last_sync_status' => 'failed', 'last_sync_error' => 'Connection failed: ' . $e->getMessage(), 'last_synced_at' => now(), ]); throw $e; } $folderName = $account->folder ?: 'INBOX'; try { $folder = $client->getFolder($folderName); } catch (Exception $e) { $account->update([ 'last_sync_status' => 'failed', 'last_sync_error' => "Folder '{$folderName}' not found: " . $e->getMessage(), 'last_synced_at' => now(), ]); $client->disconnect(); throw $e; } // Build query: unseen, UID > $maxUid, limit 10 (no ordering to avoid library errors) $query = $folder->query() ->unseen() ->leaveUnread() ->whereUid($maxUid + 1, null) ->limit(10); $messages = $query->get(); $saved = 0; foreach ($messages as $message) { $from = $message->getFrom()[0] ?? null; $attributes = [ 'imap_account_id' => $account->id, 'uid' => $message->getUid(), 'folder' => $folderName, ]; $values = [ 'message_id' => $message->getMessageId()->toString() ?: null, 'subject' => $message->getSubject()->toString() ?: '(no subject)', 'from_name' => $from?->personal ?: ($from?->mail ?? null), 'from_email' => $from?->mail ?? null, 'to' => $this->addressesToString($message->getTo()), 'cc' => $this->addressesToString($message->getCc()), 'body_text' => $message->getTextBody(), 'body_html' => $message->getHTMLBody(), 'snippet' => $this->makeSnippet($message->getTextBody()), 'has_attachments' => $message->hasAttachments(), 'is_seen' => false, 'received_at' => $message->getDate()?->toDate(), ]; EmailMessage::updateOrCreate($attributes, $values); $saved++; } $account->update([ 'last_synced_at' => now(), 'last_sync_status' => 'success', 'last_sync_error' => null, 'unread_count' => $account->emails()->where('is_seen', false)->count(), ]); $client->disconnect(); return [ 'fetched' => $messages->count(), 'saved' => $saved, ]; } protected function addressesToString($addressList): ?string { if (! $addressList) { return null; } $parts = []; foreach ($addressList as $address) { $parts[] = $address->personal ? "{$address->personal} <{$address->mail}>" : $address->mail; } return implode(', ', $parts) ?: null; } protected function makeSnippet(?string $text): ?string { if (! $text) { return null; } $clean = trim(preg_replace('/\s+/', ' ', $text)); return mb_strlen($clean) > 160 ? mb_substr($clean, 0, 160) . '…' : $clean; } }