Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/Connection/ConnectionInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand All @@ -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.
*
Expand Down
45 changes: 0 additions & 45 deletions src/Connection/Responses/MessageResponseParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,51 +6,6 @@

class MessageResponseParser
{
/**
* Get the flags from an untagged response.
*
* @return array<string, string[]>
*/
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<string, string>
*/
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<string, string>
*/
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.
*/
Expand Down
60 changes: 30 additions & 30 deletions src/MessageQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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'] ?? '',
)
);
}

Expand All @@ -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();
}

/**
Expand Down
87 changes: 0 additions & 87 deletions tests/Unit/Connection/Responses/MessageResponseParserTest.php
Original file line number Diff line number Diff line change
@@ -1,96 +1,9 @@
<?php

use DirectoryTree\ImapEngine\Connection\Responses\Data\ListData;
use DirectoryTree\ImapEngine\Connection\Responses\Data\ResponseCodeData;
use DirectoryTree\ImapEngine\Connection\Responses\MessageResponseParser;
use DirectoryTree\ImapEngine\Connection\Responses\TaggedResponse;
use DirectoryTree\ImapEngine\Connection\Responses\UntaggedResponse;
use DirectoryTree\ImapEngine\Connection\Tokens\Atom;
use DirectoryTree\ImapEngine\Connection\Tokens\Literal;

it('parses flags from untagged response', function () {
$response = new UntaggedResponse([
new Atom('*'), // Untagged marker
new Atom('1'), // Sequence number (example)
new Atom('FETCH'), // Command type
new ListData([ // Data list
new Atom('UID'),
new Atom('12345'), // The message UID
new Atom('FLAGS'),
new ListData([ // The list of flags
new Atom('\Seen'),
new Atom('\Answered'),
new Atom('$Important'), // Example custom flag
]),
]),
]);

$parsedFlags = MessageResponseParser::getFlags($response);

expect($parsedFlags)->toBe([
'12345' => ['\Seen', '\Answered', '$Important'],
]);
});

it('parses body header from untagged response', function () {
$headerContent = "From: [email protected]\r\nTo: [email protected]\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([
Expand Down