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
25 changes: 2 additions & 23 deletions src/Folder.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,6 @@

class Folder implements Arrayable, FolderInterface, JsonSerializable
{
/**
* The folder's cached capabilities.
*/
protected array $capabilities;

/**
* Constructor.
*/
Expand Down Expand Up @@ -96,8 +91,8 @@ public function messages(): MessageQuery
*/
public function idle(callable $callback, ?callable $query = null, int $timeout = 300): void
{
if (! $this->hasCapability('IDLE')) {
throw new ImapCapabilityException('IMAP server does not support IDLE');
if (! in_array('IDLE', $this->mailbox->capabilities())) {
throw new ImapCapabilityException('Unable to IDLE. IMAP server does not support IDLE capability.');
}

// The message query to use when fetching messages.
Expand Down Expand Up @@ -198,22 +193,6 @@ public function delete(): void
$this->mailbox->connection()->delete($this->path);
}

/**
* Determine if the mailbox has the given capability.
*/
protected function hasCapability(string $capability): bool
{
return in_array($capability, $this->capabilities());
}

/**
* Get and in-memory cache the mailboxes's capabilities.
*/
protected function capabilities(): array
{
return $this->capabilities ??= $this->mailbox->capabilities();
}

/**
* Get the array representation of the folder.
*/
Expand Down
11 changes: 9 additions & 2 deletions src/Mailbox.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ class Mailbox implements MailboxInterface
],
];

/**
* The cached mailbox capabilities.
*
* @see https://datatracker.ietf.org/doc/html/rfc9051#section-6.1.1
*/
protected ?array $capabilities = null;

/**
* The currently selected folder.
*/
Expand Down Expand Up @@ -194,9 +201,9 @@ public function folders(): FolderRepositoryInterface
*/
public function capabilities(): array
{
return array_map(
return $this->capabilities ??= array_map(
fn (Atom $token) => $token->value,
$this->connection()->capability()->tokensAfter(1)
$this->connection()->capability()->tokensAfter(2)
);
}

Expand Down
30 changes: 25 additions & 5 deletions src/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use BackedEnum;
use DirectoryTree\ImapEngine\Enums\ImapFlag;
use DirectoryTree\ImapEngine\Exceptions\ImapCapabilityException;
use DirectoryTree\ImapEngine\Support\Str;
use Illuminate\Contracts\Support\Arrayable;
use JsonSerializable;
Expand Down Expand Up @@ -292,12 +293,31 @@ public function copy(string $folder): void
*/
public function move(string $folder, bool $expunge = false): void
{
$this->folder->mailbox()
->connection()
->move($folder, $this->uid);
$mailbox = $this->folder->mailbox();

if ($expunge) {
$this->folder->expunge();
$capabilities = $mailbox->capabilities();

switch (true) {
case in_array('MOVE', $capabilities):
$mailbox->connection()->move($folder, $this->uid);

if ($expunge) {
$this->folder->expunge();
}

return;

case in_array('UIDPLUS', $capabilities):
$this->copy($folder);

$this->delete($expunge);

return;

default:
throw new ImapCapabilityException(
'Unable to move message. IMAP server does not support MOVE or UIDPLUS capabilities'
);
}
}

Expand Down
1 change: 0 additions & 1 deletion tests/Integration/MailboxTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
$mailbox = mailbox();

expect($mailbox->capabilities())->toBe([
'CAPABILITY',
'IMAP4rev1',
'LITERAL+',
'UIDPLUS',
Expand Down
20 changes: 20 additions & 0 deletions tests/Unit/MailboxTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,23 @@
expect($folder->path())->toBe('INBOX');
expect($folder->flags())->toBe(['\\HasNoChildren']);
});

test('capabilities', function () {
$mailbox = Mailbox::make([
'username' => 'foo',
'password' => 'bar',
]);

$mailbox->connect(ImapConnection::fake([
'* OK Welcome to IMAP',
'TAG1 OK Logged in',
'* CAPABILITY IMAP4rev1 STARTTLS AUTH=PLAIN',
'TAG2 OK CAPABILITY completed',
]));

expect($mailbox->capabilities())->toBe([
'IMAP4rev1',
'STARTTLS',
'AUTH=PLAIN',
]);
});
105 changes: 105 additions & 0 deletions tests/Unit/MessageTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php

use DirectoryTree\ImapEngine\Connection\ImapConnection;
use DirectoryTree\ImapEngine\Enums\ImapFlag;
use DirectoryTree\ImapEngine\Exceptions\ImapCapabilityException;
use DirectoryTree\ImapEngine\Folder;
use DirectoryTree\ImapEngine\Mailbox;
use DirectoryTree\ImapEngine\Message;

test('it moves message using MOVE when capable', function () {
$mailbox = Mailbox::make([
'username' => 'foo',
'password' => 'bar',
]);

$mailbox->connect(ImapConnection::fake([
'* OK Welcome to IMAP',
'TAG1 OK Logged in',
'* CAPABILITY IMAP4rev1 STARTTLS MOVE AUTH=PLAIN',
'TAG2 OK CAPABILITY completed',
'TAG3 OK MOVE completed',
]));

$folder = new Folder($mailbox, 'INBOX', [], '/');

$message = new Message($folder, 1, [], 'header', 'body');

$message->move('INBOX.Sent');
})->throwsNoExceptions();

test('it copies and then deletes message using UIDPLUS when incapable of MOVE', function () {
$mailbox = Mailbox::make([
'username' => 'foo',
'password' => 'bar',
]);

$mailbox->connect(ImapConnection::fake([
'* OK Welcome to IMAP',
'TAG1 OK Logged in',
'* CAPABILITY IMAP4rev1 STARTTLS UIDPLUS AUTH=PLAIN',
'TAG2 OK CAPABILITY completed',
'TAG3 OK UID MOVE completed',
'TAG4 OK COPY completed',
]));

$folder = new Folder($mailbox, 'INBOX', [], '/');

$message = new Message($folder, 1, [], 'header', 'body');

$message->move('INBOX.Sent');
})->throwsNoExceptions();

test('it throws exception when server does not support MOVE or UIDPLUS capabilities', function () {
$mailbox = Mailbox::make([
'username' => 'foo',
'password' => 'bar',
]);

$mailbox->connect(ImapConnection::fake([
'* OK Welcome to IMAP',
'TAG1 OK Logged in',
'* CAPABILITY IMAP4rev1 STARTTLS AUTH=PLAIN',
'TAG2 OK CAPABILITY completed',
]));

$folder = new Folder($mailbox, 'INBOX', [], '/');

$message = new Message($folder, 1, [], 'header', 'body');

$message->move('INBOX.Sent');
})->throws(ImapCapabilityException::class);

test('it can mark and unmark a message as flagged', function () {
$mailbox = Mailbox::make([
'username' => 'foo',
'password' => 'bar',
]);

$mailbox->connect(ImapConnection::fake([
'* OK Welcome to IMAP',
'TAG1 OK Logged in',
'* CAPABILITY IMAP4rev1 STARTTLS AUTH=PLAIN',
'TAG2 OK CAPABILITY completed',
'TAG3 OK STORE completed',
]));

$folder = new Folder($mailbox, 'INBOX', [], '/');

$message = new Message($folder, 1, [], 'header', 'body');

expect($message->isFlagged())->toBeFalse();
expect($message->flags())->not->toContain('\\Flagged');

$message->markFlagged();

expect($message->isFlagged())->toBeTrue();
expect($message->flags())->toContain('\\Flagged');
expect($message->hasFlag(ImapFlag::Flagged))->toBeTrue();

$message->unmarkFlagged();

expect($message->isFlagged())->toBeFalse();
expect($message->flags())->not->toContain('\\Flagged');
expect($message->hasFlag(ImapFlag::Flagged))->toBeFalse();
});