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
20 changes: 19 additions & 1 deletion src/Collections/FolderCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,28 @@

namespace DirectoryTree\ImapEngine\Collections;

use DirectoryTree\ImapEngine\Enums\ImapSpecialUse;
use DirectoryTree\ImapEngine\FolderInterface;
use Illuminate\Support\Collection;

/**
* @template-extends Collection<array-key, FolderInterface>
*/
class FolderCollection extends Collection {}
class FolderCollection extends Collection
{
/**
* Find a folder by its special use.
*/
public function findBySpecialUse(ImapSpecialUse $specialUse): ?FolderInterface
{
return $this->first(function (FolderInterface $folder) use ($specialUse) {
foreach ($folder->attributes() as $attribute) {
if (strcasecmp($attribute, $specialUse->value) === 0) {
return true;
}
}

return false;
});
}
}
2 changes: 1 addition & 1 deletion src/Connection/ConnectionInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ public function examine(string $folder): ResponseCollection;
*
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-list-command
*/
public function list(string $reference = '', string $folder = '*'): ResponseCollection;
public function list(string $reference = '', string $folder = '*', array $return = []): ResponseCollection;

/**
* Send a "STATUS" command.
Expand Down
11 changes: 9 additions & 2 deletions src/Connection/ImapConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -351,9 +351,16 @@ public function quotaRoot(string $mailbox): ResponseCollection
/**
* {@inheritDoc}
*/
public function list(string $reference = '', string $folder = '*'): ResponseCollection
public function list(string $reference = '', string $folder = '*', array $return = []): ResponseCollection
{
$this->send('LIST', Str::literal([$reference, $folder]), $tag);
$tokens = Str::literal([$reference, $folder]);

if ($return) {
$tokens[] = 'RETURN';
$tokens[] = Str::list($return);
}

$this->send('LIST', $tokens, $tag);

$this->assertTaggedResponse($tag);

Expand Down
17 changes: 17 additions & 0 deletions src/Enums/ImapSpecialUse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace DirectoryTree\ImapEngine\Enums;

/**
* @see https://datatracker.ietf.org/doc/html/rfc6154#section-2
*/
enum ImapSpecialUse: string
{
case All = '\\All';
case Archive = '\\Archive';
case Drafts = '\\Drafts';
case Flagged = '\\Flagged';
case Junk = '\\Junk';
case Sent = '\\Sent';
case Trash = '\\Trash';
}
10 changes: 5 additions & 5 deletions src/Folder.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class Folder implements Arrayable, FolderInterface, JsonSerializable
public function __construct(
protected Mailbox $mailbox,
protected string $path,
protected array $flags = [],
protected array $attributes = [],
protected string $delimiter = '/',
) {}

Expand All @@ -44,13 +44,13 @@ public function path(): string
}

/**
* Get the folder flags.
* Get the folder attributes.
*
* @return string[]
*/
public function flags(): array
public function attributes(): array
{
return $this->flags;
return $this->attributes;
}

/**
Expand Down Expand Up @@ -263,7 +263,7 @@ public function toArray(): array
{
return [
'path' => $this->path,
'flags' => $this->flags,
'attributes' => $this->attributes,
'delimiter' => $this->delimiter,
];
}
Expand Down
32 changes: 32 additions & 0 deletions src/FolderData.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace DirectoryTree\ImapEngine;

enum FolderData: string implements FolderDataItem
{
case SpecialUse = 'SPECIAL-USE';

/**
* {@inheritDoc}
*/
public function key(): string
{
return $this->value;
}

/**
* {@inheritDoc}
*/
public function toImap(): string
{
return $this->value;
}

/**
* {@inheritDoc}
*/
public function capability(): string
{
return $this->value;
}
}
21 changes: 21 additions & 0 deletions src/FolderDataItem.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace DirectoryTree\ImapEngine;

interface FolderDataItem
{
/**
* Get the unique folder data item key.
*/
public function key(): string;

/**
* Get the IMAP representation of the folder data item.
*/
public function toImap(): string;

/**
* Get the capability required to request the folder data item.
*/
public function capability(): string;
}
4 changes: 2 additions & 2 deletions src/FolderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ public function mailbox(): MailboxInterface;
public function path(): string;

/**
* Get the folder flags.
* Get the folder attributes.
*
* @return string[]
*/
public function flags(): array;
public function attributes(): array;

/**
* Get the folder delimiter.
Expand Down
34 changes: 32 additions & 2 deletions src/FolderRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,37 @@

use DirectoryTree\ImapEngine\Collections\FolderCollection;
use DirectoryTree\ImapEngine\Connection\Responses\UntaggedResponse;
use DirectoryTree\ImapEngine\Exceptions\ImapCapabilityException;
use DirectoryTree\ImapEngine\Support\Str;

class FolderRepository implements FolderRepositoryInterface
{
/**
* The data items to include in the folder LIST request.
*
* @var array<string, FolderDataItem>
*/
protected array $dataItems = [];

/**
* Constructor.
*/
public function __construct(
protected Mailbox $mailbox
) {}

/**
* {@inheritDoc}
*/
public function with(FolderDataItem ...$items): static
{
foreach ($items as $item) {
$this->dataItems[$item->key()] = $item;
}

return $this;
}

/**
* {@inheritDoc}
*/
Expand Down Expand Up @@ -56,11 +76,21 @@ public function firstOrCreate(string $path): FolderInterface
*/
public function get(?string $match = '*', ?string $reference = ''): FolderCollection
{
return $this->mailbox->connection()->list($reference, Str::toImapUtf7($match))->map(
$return = array_map(function (FolderDataItem $item) {
if (! $this->mailbox->hasCapability($item->capability())) {
throw new ImapCapabilityException(
"Unable to fetch {$item->key()} folder data. IMAP server does not support {$item->capability()} capability."
);
}

return $item->toImap();
}, $this->dataItems);

return $this->mailbox->connection()->list($reference, Str::toImapUtf7($match), $return)->map(
fn (UntaggedResponse $response) => new Folder(
mailbox: $this->mailbox,
path: $response->tokenAt(4)->value,
flags: $response->tokenAt(2)->values(),
attributes: $response->tokenAt(2)->values(),
delimiter: $response->tokenAt(3)->value,
)
)->pipeInto(FolderCollection::class);
Expand Down
5 changes: 5 additions & 0 deletions src/FolderRepositoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@

interface FolderRepositoryInterface
{
/**
* Add items to the folder LIST request.
*/
public function with(FolderDataItem ...$items): static;

/**
* Find a folder.
*/
Expand Down
12 changes: 6 additions & 6 deletions src/Testing/FakeFolder.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class FakeFolder implements FolderInterface
*/
public function __construct(
protected string $path = '',
protected array $flags = [],
protected array $attributes = [],
/** @var FakeMessage[] */
protected array $messages = [],
protected string $delimiter = '/',
Expand All @@ -44,9 +44,9 @@ public function path(): string
/**
* {@inheritDoc}
*/
public function flags(): array
public function attributes(): array
{
return $this->flags;
return $this->attributes;
}

/**
Expand Down Expand Up @@ -184,11 +184,11 @@ public function setPath(string $path): FakeFolder
}

/**
* Set the folder's flags.
* Set the folder's attributes.
*/
public function setFlags(array $flags): FakeFolder
public function setAttributes(array $attributes): FakeFolder
{
$this->flags = $flags;
$this->attributes = $attributes;

return $this;
}
Expand Down
20 changes: 20 additions & 0 deletions src/Testing/FakeFolderRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace DirectoryTree\ImapEngine\Testing;

use DirectoryTree\ImapEngine\Collections\FolderCollection;
use DirectoryTree\ImapEngine\FolderDataItem;
use DirectoryTree\ImapEngine\FolderInterface;
use DirectoryTree\ImapEngine\FolderRepositoryInterface;
use DirectoryTree\ImapEngine\MailboxInterface;
Expand All @@ -11,6 +12,13 @@

class FakeFolderRepository implements FolderRepositoryInterface
{
/**
* The requested folder data items.
*
* @var array<string, FolderDataItem>
*/
protected array $dataItems = [];

/**
* Constructor.
*/
Expand All @@ -20,6 +28,18 @@ public function __construct(
protected array $folders = []
) {}

/**
* {@inheritDoc}
*/
public function with(FolderDataItem ...$items): static
{
foreach ($items as $item) {
$this->dataItems[$item->key()] = $item;
}

return $this;
}

/**
* {@inheritDoc}
*/
Expand Down
20 changes: 20 additions & 0 deletions tests/Unit/Connection/ImapConnectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,26 @@
expect($responses->count())->toBeGreaterThan(0);
});

test('list folders with return options', function () {
$stream = new FakeStream;
$stream->open();

$stream->feed([
'* OK Welcome to IMAP',
'* LIST (\\Sent) "/" "Sent"',
'TAG1 OK LIST completed',
]);

$connection = new ImapConnection($stream);
$connection->connect('imap.example.com');

$responses = $connection->list('', '*', ['SPECIAL-USE']);

$stream->assertWritten('TAG1 LIST "" "*" RETURN (SPECIAL-USE)');

expect($responses)->toHaveCount(1);
});

test('append message', function () {
$stream = new FakeStream;
$stream->open();
Expand Down
Loading
Loading