From 17b5fa2340bbedc7bf46d4ad93a26ffb4fdfcd98 Mon Sep 17 00:00:00 2001 From: matthieu Date: Wed, 20 May 2026 07:57:17 +0200 Subject: [PATCH] =?UTF-8?q?fix(mail)=20:=20corrige=20le=20fetch=20IMAP=20r?= =?UTF-8?q?=C3=A9el=20contre=20OVH=20(listMessages=20cass=C3=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Quatre bugs révélés en testant contre une vraie boîte OVH (les tests mockaient le provider, donc jamais exercés) : - requête sans critère → "BAD parse error: zero-length content" : ajout de whereAll() - getDate()/getSubject() renvoient des Attribute webklex v6, pas des scalaires : casts explicites - séquence par défaut ST_MSGN → le peek() de webklex faisait un STORE par numéro de séquence rejeté par OVH ("flag could not be removed") : force ST_UID sur toutes les requêtes - snippet via getTextBody() forçait un fetch de corps par mail (sync 179s + peek) : setFetchBody(false) au listing, snippet désormais optionnel Sync INBOX : 9 messages en 1,6s (avant : échec en 179s). Co-Authored-By: Claude Opus 4.7 (1M context) --- src/Mail/ImapMailProvider.php | 45 ++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/src/Mail/ImapMailProvider.php b/src/Mail/ImapMailProvider.php index eac2021..e146d5a 100644 --- a/src/Mail/ImapMailProvider.php +++ b/src/Mail/ImapMailProvider.php @@ -17,6 +17,7 @@ use SodiumException; use Throwable; use Webklex\PHPIMAP\Client; use Webklex\PHPIMAP\ClientManager; +use Webklex\PHPIMAP\IMAP; final class ImapMailProvider implements MailProviderInterface { @@ -90,13 +91,19 @@ final class ImapMailProvider implements MailProviderInterface throw MailProviderException::operationFailed('listMessages', sprintf('Folder %s not found', $folderPath)); } - $messages = $folder->query()->leaveUnread()->get(); + $messages = $folder->query() + ->whereAll() + ->setFetchBody(false) + ->leaveUnread() + ->setSequence(IMAP::ST_UID) + ->get() + ; $result = []; $items = array_slice($messages->toArray(), $offset, $limit); foreach ($items as $message) { - $result[] = $this->buildHeaderDto($message); + $result[] = $this->buildHeaderDto($message, withSnippet: false); } $client->disconnect(); @@ -121,7 +128,7 @@ final class ImapMailProvider implements MailProviderInterface throw MailProviderException::operationFailed('fetchMessage', sprintf('Folder %s not found', $folderPath)); } - $message = $folder->query()->uid($uid)->leaveUnread()->get()->first(); + $message = $folder->query()->uid($uid)->leaveUnread()->setSequence(IMAP::ST_UID)->get()->first(); if (null === $message) { throw MailProviderException::operationFailed('fetchMessage', sprintf('UID %d not found in folder %s', $uid, $folderPath)); @@ -168,7 +175,7 @@ final class ImapMailProvider implements MailProviderInterface throw MailProviderException::operationFailed('markRead', sprintf('Folder %s not found', $folderPath)); } - $message = $folder->query()->uid($uid)->leaveUnread()->get()->first(); + $message = $folder->query()->uid($uid)->leaveUnread()->setSequence(IMAP::ST_UID)->get()->first(); if (null === $message) { throw MailProviderException::operationFailed('markRead', sprintf('UID %d not found', $uid)); @@ -200,7 +207,7 @@ final class ImapMailProvider implements MailProviderInterface throw MailProviderException::operationFailed('markFlagged', sprintf('Folder %s not found', $folderPath)); } - $message = $folder->query()->uid($uid)->leaveUnread()->get()->first(); + $message = $folder->query()->uid($uid)->leaveUnread()->setSequence(IMAP::ST_UID)->get()->first(); if (null === $message) { throw MailProviderException::operationFailed('markFlagged', sprintf('UID %d not found', $uid)); @@ -232,7 +239,7 @@ final class ImapMailProvider implements MailProviderInterface throw MailProviderException::operationFailed('moveMessage', sprintf('Folder %s not found', $folderPath)); } - $message = $folder->query()->uid($uid)->leaveUnread()->get()->first(); + $message = $folder->query()->uid($uid)->leaveUnread()->setSequence(IMAP::ST_UID)->get()->first(); if (null === $message) { throw MailProviderException::operationFailed('moveMessage', sprintf('UID %d not found', $uid)); @@ -259,7 +266,7 @@ final class ImapMailProvider implements MailProviderInterface throw MailProviderException::operationFailed('fetchAttachment', sprintf('Folder %s not found', $folderPath)); } - $message = $folder->query()->uid($uid)->leaveUnread()->get()->first(); + $message = $folder->query()->uid($uid)->leaveUnread()->setSequence(IMAP::ST_UID)->get()->first(); if (null === $message) { throw MailProviderException::operationFailed('fetchAttachment', sprintf('UID %d not found', $uid)); @@ -331,11 +338,11 @@ final class ImapMailProvider implements MailProviderInterface return $client; } - private function buildHeaderDto(mixed $message): MailMessageHeaderDto + private function buildHeaderDto(mixed $message, bool $withSnippet = true): MailMessageHeaderDto { $from = $message->getFrom()->first(); $fromAddress = null !== $from ? (string) $from->mail : ''; - $fromName = null !== $from ? ($from->personal ?? null) : null; + $fromName = null !== $from && null !== $from->personal ? (string) $from->personal : null; $toAddresses = []; foreach ($message->getTo() as $addr) { @@ -351,18 +358,28 @@ final class ImapMailProvider implements MailProviderInterface } } - $sentAt = $message->getDate()?->toDateTimeImmutable() ?? new DateTimeImmutable(); + $sentAt = new DateTimeImmutable(); + $dateAttr = $message->getDate(); + if (null !== $dateAttr) { + try { + $sentAt = DateTimeImmutable::createFromInterface($dateAttr->toDate()); + } catch (Throwable) { + // keep default when the header date is missing or unparsable + } + } $snippet = null; - $text = $message->getTextBody(); - if (null !== $text && '' !== $text) { - $snippet = mb_substr(strip_tags($text), 0, 200); + if ($withSnippet) { + $text = $message->getTextBody(); + if (null !== $text && '' !== $text) { + $snippet = mb_substr(strip_tags($text), 0, 200); + } } return new MailMessageHeaderDto( uid: (int) $message->getUid(), messageId: (string) $message->getMessageId(), - subject: $message->getSubject() ?: null, + subject: '' !== (string) $message->getSubject() ? (string) $message->getSubject() : null, fromAddress: $fromAddress, fromName: $fromName, toAddresses: $toAddresses,