diff --git a/src/Connection/ConnectionInterface.php b/src/Connection/ConnectionInterface.php index 2374e1a..93f111b 100644 --- a/src/Connection/ConnectionInterface.php +++ b/src/Connection/ConnectionInterface.php @@ -174,6 +174,15 @@ public function bodyPart(string $partIndex, int|array $ids, bool $peek = false): */ public function flags(int|array $ids): ResponseCollection; + /** + * Send a "FETCH" command. + * + * Fetch one or more items for one or more messages. + * + * @see https://datatracker.ietf.org/doc/html/rfc9051#name-fetch-command + */ + public function fetch(array|string $items, array|int $from, mixed $to = null, ImapFetchIdentifier $identifier = ImapFetchIdentifier::Uid): ResponseCollection; + /** * Send a "RFC822.SIZE" command. * @@ -183,6 +192,11 @@ public function flags(int|array $ids): ResponseCollection; */ public function size(int|array $ids): ResponseCollection; + /** + * Send an IMAP command. + */ + public function send(string $name, array $tokens = [], ?string &$tag = null): void; + /** * Send a "SELECT" command. * diff --git a/src/Connection/Responses/MessageResponseParser.php b/src/Connection/Responses/MessageResponseParser.php index 3e96c5a..800934f 100644 --- a/src/Connection/Responses/MessageResponseParser.php +++ b/src/Connection/Responses/MessageResponseParser.php @@ -6,51 +6,6 @@ class MessageResponseParser { - /** - * Get the flags from an untagged response. - * - * @return array - */ - public static function getFlags(UntaggedResponse $response): array - { - $data = $response->tokenAt(3); - - $uid = $data->lookup('UID')->value; - $flags = $data->lookup('FLAGS')->values(); - - return [$uid => $flags]; - } - - /** - * Get the body header from an untagged response. - * - * @return array - */ - public static function getBodyHeader(UntaggedResponse $response): array - { - $data = $response->tokenAt(3); - - $uid = $data->lookup('UID')->value; - $headers = $data->lookup('[HEADER]')->value; - - return [$uid => $headers]; - } - - /** - * Get the body text from an untagged response. - * - * @return array - */ - public static function getBodyText(UntaggedResponse $response): array - { - $data = $response->tokenAt(3); - - $uid = $data->lookup('UID')->value; - $contents = $data->lookup('[TEXT]')->value; - - return [$uid => $contents]; - } - /** * Get the UID from a tagged move or copy response. */ diff --git a/src/MessageQuery.php b/src/MessageQuery.php index b0e83b9..e790d25 100644 --- a/src/MessageQuery.php +++ b/src/MessageQuery.php @@ -6,7 +6,6 @@ use DirectoryTree\ImapEngine\Collections\ResponseCollection; use DirectoryTree\ImapEngine\Connection\ConnectionInterface; use DirectoryTree\ImapEngine\Connection\ImapQueryBuilder; -use DirectoryTree\ImapEngine\Connection\Responses\MessageResponseParser; use DirectoryTree\ImapEngine\Connection\Responses\UntaggedResponse; use DirectoryTree\ImapEngine\Connection\Tokens\Token; use DirectoryTree\ImapEngine\Enums\ImapFetchIdentifier; @@ -238,15 +237,14 @@ protected function populate(Collection $uids): MessageCollection $messages->total($uids->count()); - $rawMessages = $this->fetch($uids); - - foreach ($rawMessages['uids'] as $uid) { - $flags = $rawMessages['flags'][$uid] ?? []; - $headers = $rawMessages['headers'][$uid] ?? ''; - $contents = $rawMessages['contents'][$uid] ?? ''; - + foreach ($this->fetch($uids) as $uid => $response) { $messages->push( - $this->newMessage($uid, $flags, $headers, $contents) + $this->newMessage( + $uid, + $response['flags'] ?? [], + $response['headers'] ?? '', + $response['contents'] ?? '', + ) ); } @@ -267,27 +265,29 @@ protected function fetch(Collection $messages): array ->values() ->all(); - $flags = $this->fetchFlags ? $this->connection() - ->flags($uids) - ->mapWithKeys(MessageResponseParser::getFlags(...)) - ->all() : []; - - $headers = $this->fetchHeaders ? $this->connection() - ->bodyHeader($uids, $this->fetchAsUnread) - ->mapWithKeys(MessageResponseParser::getBodyHeader(...)) - ->all() : []; - - $contents = $this->fetchBody ? $this->connection() - ->bodyText($uids, $this->fetchAsUnread) - ->mapWithKeys(MessageResponseParser::getBodyText(...)) - ->all() : []; - - return [ - 'uids' => $uids, - 'flags' => $flags, - 'headers' => $headers, - 'contents' => $contents, - ]; + $response = $this->connection()->fetch(array_filter([ + $this->fetchFlags ? 'FLAGS' : null, + $this->fetchBody ? $this->fetchAsUnread + ? 'BODY.PEEK[TEXT]' + : 'BODY[TEXT]' : null, + $this->fetchHeaders ? $this->fetchAsUnread + ? 'BODY.PEEK[HEADER]' + : 'BODY[HEADER]' : null, + ]), $uids); + + return $response->mapWithKeys(function (UntaggedResponse $response) { + $data = $response->tokenAt(3); + + $uid = $data->lookup('UID')->value; + + return [ + $uid => [ + 'flags' => $data->lookup('FLAGS')?->values() ?? [], + 'headers' => $data->lookup('[HEADER]')?->value ?? '', + 'contents' => $data->lookup('[TEXT]')?->value ?? '', + ], + ]; + })->all(); } /** diff --git a/tests/Unit/Connection/Responses/MessageResponseParserTest.php b/tests/Unit/Connection/Responses/MessageResponseParserTest.php index 8275472..67a6632 100644 --- a/tests/Unit/Connection/Responses/MessageResponseParserTest.php +++ b/tests/Unit/Connection/Responses/MessageResponseParserTest.php @@ -1,96 +1,9 @@ toBe([ - '12345' => ['\Seen', '\Answered', '$Important'], - ]); -}); - -it('parses body header from untagged response', function () { - $headerContent = "From: sender@example.com\r\nTo: recipient@example.com\r\nSubject: Test Email Header\r\n"; - - $response = new UntaggedResponse([ - new Atom('*'), // Untagged marker - new Atom('2'), // Sequence number (example) - new Atom('FETCH'), // Command type - new ListData([ // Data list - new Atom('UID'), - new Atom('54321'), // The message UID - new Atom('BODY'), - new Atom('[HEADER]'), // Specifies header part - new Literal($headerContent), // The header content as a literal - ]), - ]); - - $parsedHeader = MessageResponseParser::getBodyHeader($response); - - expect($parsedHeader)->toBe(['54321' => $headerContent]); -}); - -it('parses body text from untagged response', function () { - $textContent = "This is the plain text body of the email.\r\nIt might have multiple lines.\r\n"; - - $response = new UntaggedResponse([ - new Atom('*'), // Untagged marker - new Atom('3'), // Sequence number (example) - new Atom('FETCH'), // Command type - new ListData([ // Data list - new Atom('UID'), - new Atom('98765'), // The message UID - new Atom('BODY'), - new Atom('[TEXT]'), // Specifies text part - new Literal($textContent), // The text content as a literal - ]), - ]); - - $parsedText = MessageResponseParser::getBodyText($response); - - expect($parsedText)->toBe(['98765' => $textContent]); -}); - -it('handles empty flags list correctly', function () { - $response = new UntaggedResponse([ - new Atom('*'), - new Atom('4'), - new Atom('FETCH'), - new ListData([ - new Atom('UID'), - new Atom('11111'), - new Atom('FLAGS'), - new ListData([]), // Empty flags list - ]), - ]); - - $parsedFlags = MessageResponseParser::getFlags($response); - - expect($parsedFlags)->toBe(['11111' => []]); -}); it('parses UID from tagged COPYUID response', function () { $response = new TaggedResponse([