From f0e97e0cb9fa9e1026bb7f046eb88e695ba52219 Mon Sep 17 00:00:00 2001 From: Steve Bauman Date: Wed, 2 Sep 2026 11:55:34 -0400 Subject: [PATCH 1/6] Add special-use folder resolution --- src/Collections/FolderCollection.php | 20 ++- src/Connection/ConnectionInterface.php | 2 +- src/Connection/ImapConnection.php | 11 +- src/Enums/ImapSpecialUse.php | 35 +++++ src/Folder.php | 2 +- src/FolderInterface.php | 14 ++ src/FolderRepository.php | 18 ++- src/FolderRepositoryInterface.php | 43 +++++- src/HasSpecialUses.php | 42 ++++++ src/ResolvesSpecialUseFolders.php | 78 +++++++++++ src/Testing/FakeFolder.php | 3 +- src/Testing/FakeFolderRepository.php | 13 +- tests/Unit/Connection/ImapConnectionTest.php | 20 +++ tests/Unit/FolderRepositoryTest.php | 123 ++++++++++++++++++ tests/Unit/FolderTest.php | 16 +++ .../Unit/Testing/FakeFolderRepositoryTest.php | 26 ++++ 16 files changed, 456 insertions(+), 10 deletions(-) create mode 100644 src/Enums/ImapSpecialUse.php create mode 100644 src/HasSpecialUses.php create mode 100644 src/ResolvesSpecialUseFolders.php create mode 100644 tests/Unit/FolderRepositoryTest.php diff --git a/src/Collections/FolderCollection.php b/src/Collections/FolderCollection.php index 650bd9e..7c39637 100644 --- a/src/Collections/FolderCollection.php +++ b/src/Collections/FolderCollection.php @@ -2,10 +2,28 @@ namespace DirectoryTree\ImapEngine\Collections; +use DirectoryTree\ImapEngine\Enums\ImapSpecialUse; use DirectoryTree\ImapEngine\FolderInterface; use Illuminate\Support\Collection; /** * @template-extends Collection */ -class FolderCollection extends Collection {} +class FolderCollection extends Collection +{ + /** + * Find a folder by its special use. + */ + public function findBySpecialUse(ImapSpecialUse $specialUse): ?FolderInterface + { + return $this->first( + fn (FolderInterface $folder) => $folder->hasSpecialUse($specialUse) + ) ?? $this->first( + fn (FolderInterface $folder) => in_array( + strtolower($folder->name()), + $specialUse->fallbackNames(), + true + ) + ); + } +} diff --git a/src/Connection/ConnectionInterface.php b/src/Connection/ConnectionInterface.php index 2cf5dd4..32ec711 100644 --- a/src/Connection/ConnectionInterface.php +++ b/src/Connection/ConnectionInterface.php @@ -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. diff --git a/src/Connection/ImapConnection.php b/src/Connection/ImapConnection.php index 826dc42..7080f66 100644 --- a/src/Connection/ImapConnection.php +++ b/src/Connection/ImapConnection.php @@ -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); diff --git a/src/Enums/ImapSpecialUse.php b/src/Enums/ImapSpecialUse.php new file mode 100644 index 0000000..36418c2 --- /dev/null +++ b/src/Enums/ImapSpecialUse.php @@ -0,0 +1,35 @@ + ['all', 'all mail', 'all messages'], + self::Archive => ['archive', 'archives'], + self::Drafts => ['draft', 'drafts'], + self::Flagged => ['flagged', 'starred'], + self::Junk => ['junk', 'junk mail', 'junk email', 'spam', 'bulk mail'], + self::Sent => ['sent', 'sent mail', 'sent items', 'sent messages'], + self::Trash => ['trash', 'deleted', 'deleted items', 'deleted messages', 'bin'], + }; + } +} diff --git a/src/Folder.php b/src/Folder.php index 0646fe1..b578aa5 100644 --- a/src/Folder.php +++ b/src/Folder.php @@ -15,7 +15,7 @@ class Folder implements Arrayable, FolderInterface, JsonSerializable { - use ComparesFolders; + use ComparesFolders, HasSpecialUses; /** * Constructor. diff --git a/src/FolderInterface.php b/src/FolderInterface.php index 9958dd2..c0321fc 100644 --- a/src/FolderInterface.php +++ b/src/FolderInterface.php @@ -2,6 +2,8 @@ namespace DirectoryTree\ImapEngine; +use DirectoryTree\ImapEngine\Enums\ImapSpecialUse; + interface FolderInterface { /** @@ -21,6 +23,18 @@ public function path(): string; */ public function flags(): array; + /** + * Get the folder's special uses. + * + * @return ImapSpecialUse[] + */ + public function specialUses(): array; + + /** + * Determine if the folder has the given special use. + */ + public function hasSpecialUse(ImapSpecialUse $specialUse): bool; + /** * Get the folder delimiter. */ diff --git a/src/FolderRepository.php b/src/FolderRepository.php index d20419f..9f0a9b4 100644 --- a/src/FolderRepository.php +++ b/src/FolderRepository.php @@ -8,6 +8,8 @@ class FolderRepository implements FolderRepositoryInterface { + use ResolvesSpecialUseFolders; + /** * Constructor. */ @@ -54,9 +56,9 @@ public function firstOrCreate(string $path): FolderInterface /** * {@inheritDoc} */ - public function get(?string $match = '*', ?string $reference = ''): FolderCollection + public function get(?string $match = '*', ?string $reference = '', array $return = []): FolderCollection { - return $this->mailbox->connection()->list($reference, Str::toImapUtf7($match))->map( + return $this->mailbox->connection()->list($reference, Str::toImapUtf7($match), $return)->map( fn (UntaggedResponse $response) => new Folder( mailbox: $this->mailbox, path: $response->tokenAt(4)->value, @@ -65,4 +67,16 @@ public function get(?string $match = '*', ?string $reference = ''): FolderCollec ) )->pipeInto(FolderCollection::class); } + + /** + * Get folders with their special-use attributes when supported. + */ + protected function foldersForSpecialUse(): FolderCollection + { + $return = $this->mailbox->hasCapability('SPECIAL-USE') + ? ['SPECIAL-USE'] + : []; + + return $this->get(return: $return); + } } diff --git a/src/FolderRepositoryInterface.php b/src/FolderRepositoryInterface.php index a3a85c4..fbd1c88 100644 --- a/src/FolderRepositoryInterface.php +++ b/src/FolderRepositoryInterface.php @@ -3,6 +3,7 @@ namespace DirectoryTree\ImapEngine; use DirectoryTree\ImapEngine\Collections\FolderCollection; +use DirectoryTree\ImapEngine\Enums\ImapSpecialUse; interface FolderRepositoryInterface { @@ -26,8 +27,48 @@ public function create(string $path): FolderInterface; */ public function firstOrCreate(string $path): FolderInterface; + /** + * Find a folder by its special use. + */ + public function findBySpecialUse(ImapSpecialUse $specialUse): ?FolderInterface; + + /** + * Get the folder containing all messages. + */ + public function allMail(): ?FolderInterface; + + /** + * Get the archive folder. + */ + public function archive(): ?FolderInterface; + + /** + * Get the drafts folder. + */ + public function drafts(): ?FolderInterface; + + /** + * Get the flagged folder. + */ + public function flagged(): ?FolderInterface; + + /** + * Get the junk folder. + */ + public function junk(): ?FolderInterface; + + /** + * Get the sent folder. + */ + public function sent(): ?FolderInterface; + + /** + * Get the trash folder. + */ + public function trash(): ?FolderInterface; + /** * Get the mailboxes folders. */ - public function get(?string $match = '*', ?string $reference = ''): FolderCollection; + public function get(?string $match = '*', ?string $reference = '', array $return = []): FolderCollection; } diff --git a/src/HasSpecialUses.php b/src/HasSpecialUses.php new file mode 100644 index 0000000..197e05f --- /dev/null +++ b/src/HasSpecialUses.php @@ -0,0 +1,42 @@ + $this->hasSpecialUse($specialUse) + )); + } + + /** + * Determine if the folder has the given special use. + */ + public function hasSpecialUse(ImapSpecialUse $specialUse): bool + { + foreach ($this->flags() as $flag) { + if (strcasecmp($flag, $specialUse->value) === 0) { + return true; + } + } + + return false; + } + + /** + * Get the folder's flags. + * + * @return string[] + */ + abstract public function flags(): array; +} diff --git a/src/ResolvesSpecialUseFolders.php b/src/ResolvesSpecialUseFolders.php new file mode 100644 index 0000000..c0267a0 --- /dev/null +++ b/src/ResolvesSpecialUseFolders.php @@ -0,0 +1,78 @@ +foldersForSpecialUse()->findBySpecialUse($specialUse); + } + + /** + * Get the folder containing all messages. + */ + public function allMail(): ?FolderInterface + { + return $this->findBySpecialUse(ImapSpecialUse::All); + } + + /** + * Get the archive folder. + */ + public function archive(): ?FolderInterface + { + return $this->findBySpecialUse(ImapSpecialUse::Archive); + } + + /** + * Get the drafts folder. + */ + public function drafts(): ?FolderInterface + { + return $this->findBySpecialUse(ImapSpecialUse::Drafts); + } + + /** + * Get the flagged folder. + */ + public function flagged(): ?FolderInterface + { + return $this->findBySpecialUse(ImapSpecialUse::Flagged); + } + + /** + * Get the junk folder. + */ + public function junk(): ?FolderInterface + { + return $this->findBySpecialUse(ImapSpecialUse::Junk); + } + + /** + * Get the sent folder. + */ + public function sent(): ?FolderInterface + { + return $this->findBySpecialUse(ImapSpecialUse::Sent); + } + + /** + * Get the trash folder. + */ + public function trash(): ?FolderInterface + { + return $this->findBySpecialUse(ImapSpecialUse::Trash); + } + + /** + * Get the folders used to resolve special uses. + */ + abstract protected function foldersForSpecialUse(): FolderCollection; +} diff --git a/src/Testing/FakeFolder.php b/src/Testing/FakeFolder.php index 45d14d1..fb25af2 100644 --- a/src/Testing/FakeFolder.php +++ b/src/Testing/FakeFolder.php @@ -5,13 +5,14 @@ use DirectoryTree\ImapEngine\ComparesFolders; use DirectoryTree\ImapEngine\Exceptions\Exception; use DirectoryTree\ImapEngine\FolderInterface; +use DirectoryTree\ImapEngine\HasSpecialUses; use DirectoryTree\ImapEngine\MailboxInterface; use DirectoryTree\ImapEngine\MessageQueryInterface; use DirectoryTree\ImapEngine\Support\Str; class FakeFolder implements FolderInterface { - use ComparesFolders; + use ComparesFolders, HasSpecialUses; /** * Constructor. diff --git a/src/Testing/FakeFolderRepository.php b/src/Testing/FakeFolderRepository.php index 9ca38f8..81cc8b7 100644 --- a/src/Testing/FakeFolderRepository.php +++ b/src/Testing/FakeFolderRepository.php @@ -6,11 +6,14 @@ use DirectoryTree\ImapEngine\FolderInterface; use DirectoryTree\ImapEngine\FolderRepositoryInterface; use DirectoryTree\ImapEngine\MailboxInterface; +use DirectoryTree\ImapEngine\ResolvesSpecialUseFolders; use DirectoryTree\ImapEngine\Support\Str; use Illuminate\Support\ItemNotFoundException; class FakeFolderRepository implements FolderRepositoryInterface { + use ResolvesSpecialUseFolders; + /** * Constructor. */ @@ -61,7 +64,7 @@ public function firstOrCreate(string $path): FolderInterface /** * {@inheritDoc} */ - public function get(?string $match = '*', ?string $reference = ''): FolderCollection + public function get(?string $match = '*', ?string $reference = '', array $return = []): FolderCollection { $folders = FolderCollection::make($this->folders); @@ -74,4 +77,12 @@ public function get(?string $match = '*', ?string $reference = ''): FolderCollec return $folders; } + + /** + * Get the folders used to resolve special uses. + */ + protected function foldersForSpecialUse(): FolderCollection + { + return $this->get(); + } } diff --git a/tests/Unit/Connection/ImapConnectionTest.php b/tests/Unit/Connection/ImapConnectionTest.php index d8bf6d9..c0aa9b5 100644 --- a/tests/Unit/Connection/ImapConnectionTest.php +++ b/tests/Unit/Connection/ImapConnectionTest.php @@ -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(); diff --git a/tests/Unit/FolderRepositoryTest.php b/tests/Unit/FolderRepositoryTest.php new file mode 100644 index 0000000..446812e --- /dev/null +++ b/tests/Unit/FolderRepositoryTest.php @@ -0,0 +1,123 @@ +feed([ + '* OK Welcome to IMAP', + 'TAG1 OK Logged in', + '* CAPABILITY IMAP4rev1 SPECIAL-USE', + 'TAG2 OK CAPABILITY completed', + '* LIST (\\Sent \\HasNoChildren) "/" "Outgoing"', + 'TAG3 OK LIST completed', + ]); + + $mailbox = Mailbox::make([ + 'host' => 'imap.example.com', + 'username' => 'foo', + 'password' => 'bar', + ]); + + $mailbox->connect(new ImapConnection($stream)); + + expect($mailbox->folders()->sent()?->path())->toBe('Outgoing'); + + $stream->assertWritten('TAG3 LIST "" "*" RETURN (SPECIAL-USE)'); +}); + +test('it falls back to conventional folder names', function () { + $stream = new FakeStream; + $stream->feed([ + '* OK Welcome to IMAP', + 'TAG1 OK Logged in', + '* CAPABILITY IMAP4rev1', + 'TAG2 OK CAPABILITY completed', + '* LIST (\\HasNoChildren) "/" "Sent Items"', + 'TAG3 OK LIST completed', + ]); + + $mailbox = Mailbox::make([ + 'host' => 'imap.example.com', + 'username' => 'foo', + 'password' => 'bar', + ]); + + $mailbox->connect(new ImapConnection($stream)); + + expect($mailbox->folders()->sent()?->path())->toBe('Sent Items'); + + $stream->assertWritten('TAG3 LIST "" "*"'); + $stream->assertNotWritten('TAG3 LIST "" "*" RETURN (SPECIAL-USE)'); +}); + +test('it resolves special-use attributes from an ordinary list response', function () { + $stream = new FakeStream; + $stream->feed([ + '* OK Welcome to IMAP', + 'TAG1 OK Logged in', + '* CAPABILITY IMAP4rev1', + 'TAG2 OK CAPABILITY completed', + '* LIST (\\Sent \\HasNoChildren) "/" "Outgoing"', + 'TAG3 OK LIST completed', + ]); + + $mailbox = Mailbox::make([ + 'host' => 'imap.example.com', + 'username' => 'foo', + 'password' => 'bar', + ]); + + $mailbox->connect(new ImapConnection($stream)); + + expect($mailbox->folders()->sent()?->path())->toBe('Outgoing'); + + $stream->assertWritten('TAG3 LIST "" "*"'); +}); + +test('it prefers special-use attributes over folder names', function () { + $stream = new FakeStream; + $stream->feed([ + '* OK Welcome to IMAP', + 'TAG1 OK Logged in', + '* CAPABILITY IMAP4rev1 SPECIAL-USE', + 'TAG2 OK CAPABILITY completed', + '* LIST (\\HasNoChildren) "/" "Sent"', + '* LIST (\\Sent \\HasNoChildren) "/" "Outgoing"', + 'TAG3 OK LIST completed', + ]); + + $mailbox = Mailbox::make([ + 'host' => 'imap.example.com', + 'username' => 'foo', + 'password' => 'bar', + ]); + + $mailbox->connect(new ImapConnection($stream)); + + expect($mailbox->folders()->sent()?->path())->toBe('Outgoing'); +}); + +test('it returns null when a special-use folder cannot be resolved', function () { + $stream = new FakeStream; + $stream->feed([ + '* OK Welcome to IMAP', + 'TAG1 OK Logged in', + '* CAPABILITY IMAP4rev1', + 'TAG2 OK CAPABILITY completed', + '* LIST (\\HasNoChildren) "/" "INBOX"', + 'TAG3 OK LIST completed', + ]); + + $mailbox = Mailbox::make([ + 'host' => 'imap.example.com', + 'username' => 'foo', + 'password' => 'bar', + ]); + + $mailbox->connect(new ImapConnection($stream)); + + expect($mailbox->folders()->archive())->toBeNull(); +}); diff --git a/tests/Unit/FolderTest.php b/tests/Unit/FolderTest.php index ebdf8f1..326cf37 100644 --- a/tests/Unit/FolderTest.php +++ b/tests/Unit/FolderTest.php @@ -1,6 +1,7 @@ name())->toBe($mixedUtf8FolderName); }); +test('it resolves special uses from folder flags', function () { + $folder = new Folder( + mailbox: Mailbox::make(), + path: 'Sent', + flags: ['\\HasNoChildren', '\\sent', '\\Archive'], + ); + + expect($folder->specialUses())->toBe([ + ImapSpecialUse::Archive, + ImapSpecialUse::Sent, + ]); + expect($folder->hasSpecialUse(ImapSpecialUse::Sent))->toBeTrue(); + expect($folder->hasSpecialUse(ImapSpecialUse::Trash))->toBeFalse(); +}); + test('it returns quota data for the mailbox', function () { $mailbox = Mailbox::make([ 'username' => 'foo', diff --git a/tests/Unit/Testing/FakeFolderRepositoryTest.php b/tests/Unit/Testing/FakeFolderRepositoryTest.php index db915a1..b6b0445 100644 --- a/tests/Unit/Testing/FakeFolderRepositoryTest.php +++ b/tests/Unit/Testing/FakeFolderRepositoryTest.php @@ -1,6 +1,7 @@ get('nonexistent*'); expect($noMatches)->toBeEmpty(); }); + +test('it resolves special-use folders', function () { + $mailbox = new FakeMailbox; + $sentByName = new FakeFolder('Sent'); + $sentByAttribute = new FakeFolder('Outgoing', [ImapSpecialUse::Sent->value]); + + $repository = new FakeFolderRepository($mailbox, [ + $sentByName, + $sentByAttribute, + new FakeFolder('Drafts'), + new FakeFolder('Starred'), + new FakeFolder('Junk Email'), + new FakeFolder('Deleted Items'), + new FakeFolder('Archive'), + new FakeFolder('[Gmail]/All Mail'), + ]); + + expect($repository->sent())->toBe($sentByAttribute); + expect($repository->drafts()?->name())->toBe('Drafts'); + expect($repository->flagged()?->name())->toBe('Starred'); + expect($repository->junk()?->name())->toBe('Junk Email'); + expect($repository->trash()?->name())->toBe('Deleted Items'); + expect($repository->archive()?->name())->toBe('Archive'); + expect($repository->allMail()?->name())->toBe('All Mail'); +}); From aa92d547a8068b5bae2a0d3becade08cb66b5ca4 Mon Sep 17 00:00:00 2001 From: Steve Bauman Date: Wed, 2 Sep 2026 12:11:04 -0400 Subject: [PATCH 2/6] Remove special-use folder name fallbacks --- src/Collections/FolderCollection.php | 6 ------ src/Enums/ImapSpecialUse.php | 18 ------------------ tests/Unit/FolderRepositoryTest.php | 6 +++--- .../Unit/Testing/FakeFolderRepositoryTest.php | 12 ++++++------ 4 files changed, 9 insertions(+), 33 deletions(-) diff --git a/src/Collections/FolderCollection.php b/src/Collections/FolderCollection.php index 7c39637..8b8aec8 100644 --- a/src/Collections/FolderCollection.php +++ b/src/Collections/FolderCollection.php @@ -18,12 +18,6 @@ public function findBySpecialUse(ImapSpecialUse $specialUse): ?FolderInterface { return $this->first( fn (FolderInterface $folder) => $folder->hasSpecialUse($specialUse) - ) ?? $this->first( - fn (FolderInterface $folder) => in_array( - strtolower($folder->name()), - $specialUse->fallbackNames(), - true - ) ); } } diff --git a/src/Enums/ImapSpecialUse.php b/src/Enums/ImapSpecialUse.php index 36418c2..95177b7 100644 --- a/src/Enums/ImapSpecialUse.php +++ b/src/Enums/ImapSpecialUse.php @@ -14,22 +14,4 @@ enum ImapSpecialUse: string case Junk = '\\Junk'; case Sent = '\\Sent'; case Trash = '\\Trash'; - - /** - * Get conventional folder names for the special use. - * - * @return string[] - */ - public function fallbackNames(): array - { - return match ($this) { - self::All => ['all', 'all mail', 'all messages'], - self::Archive => ['archive', 'archives'], - self::Drafts => ['draft', 'drafts'], - self::Flagged => ['flagged', 'starred'], - self::Junk => ['junk', 'junk mail', 'junk email', 'spam', 'bulk mail'], - self::Sent => ['sent', 'sent mail', 'sent items', 'sent messages'], - self::Trash => ['trash', 'deleted', 'deleted items', 'deleted messages', 'bin'], - }; - } } diff --git a/tests/Unit/FolderRepositoryTest.php b/tests/Unit/FolderRepositoryTest.php index 446812e..885a7ec 100644 --- a/tests/Unit/FolderRepositoryTest.php +++ b/tests/Unit/FolderRepositoryTest.php @@ -28,7 +28,7 @@ $stream->assertWritten('TAG3 LIST "" "*" RETURN (SPECIAL-USE)'); }); -test('it falls back to conventional folder names', function () { +test('it does not infer special uses from folder names', function () { $stream = new FakeStream; $stream->feed([ '* OK Welcome to IMAP', @@ -47,7 +47,7 @@ $mailbox->connect(new ImapConnection($stream)); - expect($mailbox->folders()->sent()?->path())->toBe('Sent Items'); + expect($mailbox->folders()->sent())->toBeNull(); $stream->assertWritten('TAG3 LIST "" "*"'); $stream->assertNotWritten('TAG3 LIST "" "*" RETURN (SPECIAL-USE)'); @@ -77,7 +77,7 @@ $stream->assertWritten('TAG3 LIST "" "*"'); }); -test('it prefers special-use attributes over folder names', function () { +test('it resolves special-use attributes instead of matching folder names', function () { $stream = new FakeStream; $stream->feed([ '* OK Welcome to IMAP', diff --git a/tests/Unit/Testing/FakeFolderRepositoryTest.php b/tests/Unit/Testing/FakeFolderRepositoryTest.php index b6b0445..ee8edc7 100644 --- a/tests/Unit/Testing/FakeFolderRepositoryTest.php +++ b/tests/Unit/Testing/FakeFolderRepositoryTest.php @@ -109,12 +109,12 @@ $repository = new FakeFolderRepository($mailbox, [ $sentByName, $sentByAttribute, - new FakeFolder('Drafts'), - new FakeFolder('Starred'), - new FakeFolder('Junk Email'), - new FakeFolder('Deleted Items'), - new FakeFolder('Archive'), - new FakeFolder('[Gmail]/All Mail'), + new FakeFolder('Drafts', [ImapSpecialUse::Drafts->value]), + new FakeFolder('Starred', [ImapSpecialUse::Flagged->value]), + new FakeFolder('Junk Email', [ImapSpecialUse::Junk->value]), + new FakeFolder('Deleted Items', [ImapSpecialUse::Trash->value]), + new FakeFolder('Archive', [ImapSpecialUse::Archive->value]), + new FakeFolder('[Gmail]/All Mail', [ImapSpecialUse::All->value]), ]); expect($repository->sent())->toBe($sentByAttribute); From d28305a5c24c39a66a9d253f56d782cbf348dac4 Mon Sep 17 00:00:00 2001 From: Steve Bauman Date: Wed, 2 Sep 2026 12:51:00 -0400 Subject: [PATCH 3/6] Add folder data builder --- src/FolderData.php | 32 ++++++++ src/FolderDataItem.php | 21 +++++ src/FolderRepository.php | 44 +++++++---- src/FolderRepositoryInterface.php | 49 ++---------- src/ResolvesSpecialUseFolders.php | 78 ------------------- src/Testing/FakeFolderRepository.php | 31 +++++--- tests/Unit/FolderRepositoryTest.php | 66 ++++++++++++---- .../Unit/Testing/FakeFolderRepositoryTest.php | 21 +++-- 8 files changed, 172 insertions(+), 170 deletions(-) create mode 100644 src/FolderData.php create mode 100644 src/FolderDataItem.php delete mode 100644 src/ResolvesSpecialUseFolders.php diff --git a/src/FolderData.php b/src/FolderData.php new file mode 100644 index 0000000..eaaedd3 --- /dev/null +++ b/src/FolderData.php @@ -0,0 +1,32 @@ +value; + } + + /** + * {@inheritDoc} + */ + public function toImap(): string + { + return $this->value; + } + + /** + * {@inheritDoc} + */ + public function capability(): string + { + return $this->value; + } +} diff --git a/src/FolderDataItem.php b/src/FolderDataItem.php new file mode 100644 index 0000000..bf4b8f2 --- /dev/null +++ b/src/FolderDataItem.php @@ -0,0 +1,21 @@ + + */ + protected array $dataItems = []; /** * Constructor. @@ -17,6 +23,18 @@ 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} */ @@ -56,8 +74,18 @@ public function firstOrCreate(string $path): FolderInterface /** * {@inheritDoc} */ - public function get(?string $match = '*', ?string $reference = '', array $return = []): FolderCollection + public function get(?string $match = '*', ?string $reference = ''): FolderCollection { + $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, @@ -67,16 +95,4 @@ public function get(?string $match = '*', ?string $reference = '', array $return ) )->pipeInto(FolderCollection::class); } - - /** - * Get folders with their special-use attributes when supported. - */ - protected function foldersForSpecialUse(): FolderCollection - { - $return = $this->mailbox->hasCapability('SPECIAL-USE') - ? ['SPECIAL-USE'] - : []; - - return $this->get(return: $return); - } } diff --git a/src/FolderRepositoryInterface.php b/src/FolderRepositoryInterface.php index fbd1c88..e01c514 100644 --- a/src/FolderRepositoryInterface.php +++ b/src/FolderRepositoryInterface.php @@ -3,10 +3,13 @@ namespace DirectoryTree\ImapEngine; use DirectoryTree\ImapEngine\Collections\FolderCollection; -use DirectoryTree\ImapEngine\Enums\ImapSpecialUse; - interface FolderRepositoryInterface { + /** + * Add items to the folder LIST request. + */ + public function with(FolderDataItem ...$items): static; + /** * Find a folder. */ @@ -27,48 +30,8 @@ public function create(string $path): FolderInterface; */ public function firstOrCreate(string $path): FolderInterface; - /** - * Find a folder by its special use. - */ - public function findBySpecialUse(ImapSpecialUse $specialUse): ?FolderInterface; - - /** - * Get the folder containing all messages. - */ - public function allMail(): ?FolderInterface; - - /** - * Get the archive folder. - */ - public function archive(): ?FolderInterface; - - /** - * Get the drafts folder. - */ - public function drafts(): ?FolderInterface; - - /** - * Get the flagged folder. - */ - public function flagged(): ?FolderInterface; - - /** - * Get the junk folder. - */ - public function junk(): ?FolderInterface; - - /** - * Get the sent folder. - */ - public function sent(): ?FolderInterface; - - /** - * Get the trash folder. - */ - public function trash(): ?FolderInterface; - /** * Get the mailboxes folders. */ - public function get(?string $match = '*', ?string $reference = '', array $return = []): FolderCollection; + public function get(?string $match = '*', ?string $reference = ''): FolderCollection; } diff --git a/src/ResolvesSpecialUseFolders.php b/src/ResolvesSpecialUseFolders.php deleted file mode 100644 index c0267a0..0000000 --- a/src/ResolvesSpecialUseFolders.php +++ /dev/null @@ -1,78 +0,0 @@ -foldersForSpecialUse()->findBySpecialUse($specialUse); - } - - /** - * Get the folder containing all messages. - */ - public function allMail(): ?FolderInterface - { - return $this->findBySpecialUse(ImapSpecialUse::All); - } - - /** - * Get the archive folder. - */ - public function archive(): ?FolderInterface - { - return $this->findBySpecialUse(ImapSpecialUse::Archive); - } - - /** - * Get the drafts folder. - */ - public function drafts(): ?FolderInterface - { - return $this->findBySpecialUse(ImapSpecialUse::Drafts); - } - - /** - * Get the flagged folder. - */ - public function flagged(): ?FolderInterface - { - return $this->findBySpecialUse(ImapSpecialUse::Flagged); - } - - /** - * Get the junk folder. - */ - public function junk(): ?FolderInterface - { - return $this->findBySpecialUse(ImapSpecialUse::Junk); - } - - /** - * Get the sent folder. - */ - public function sent(): ?FolderInterface - { - return $this->findBySpecialUse(ImapSpecialUse::Sent); - } - - /** - * Get the trash folder. - */ - public function trash(): ?FolderInterface - { - return $this->findBySpecialUse(ImapSpecialUse::Trash); - } - - /** - * Get the folders used to resolve special uses. - */ - abstract protected function foldersForSpecialUse(): FolderCollection; -} diff --git a/src/Testing/FakeFolderRepository.php b/src/Testing/FakeFolderRepository.php index 81cc8b7..e673c6c 100644 --- a/src/Testing/FakeFolderRepository.php +++ b/src/Testing/FakeFolderRepository.php @@ -3,16 +3,21 @@ 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; -use DirectoryTree\ImapEngine\ResolvesSpecialUseFolders; use DirectoryTree\ImapEngine\Support\Str; use Illuminate\Support\ItemNotFoundException; class FakeFolderRepository implements FolderRepositoryInterface { - use ResolvesSpecialUseFolders; + /** + * The requested folder data items. + * + * @var array + */ + protected array $dataItems = []; /** * Constructor. @@ -23,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} */ @@ -64,7 +81,7 @@ public function firstOrCreate(string $path): FolderInterface /** * {@inheritDoc} */ - public function get(?string $match = '*', ?string $reference = '', array $return = []): FolderCollection + public function get(?string $match = '*', ?string $reference = ''): FolderCollection { $folders = FolderCollection::make($this->folders); @@ -77,12 +94,4 @@ public function get(?string $match = '*', ?string $reference = '', array $return return $folders; } - - /** - * Get the folders used to resolve special uses. - */ - protected function foldersForSpecialUse(): FolderCollection - { - return $this->get(); - } } diff --git a/tests/Unit/FolderRepositoryTest.php b/tests/Unit/FolderRepositoryTest.php index 885a7ec..f8d6b38 100644 --- a/tests/Unit/FolderRepositoryTest.php +++ b/tests/Unit/FolderRepositoryTest.php @@ -2,6 +2,9 @@ use DirectoryTree\ImapEngine\Connection\ImapConnection; use DirectoryTree\ImapEngine\Connection\Streams\FakeStream; +use DirectoryTree\ImapEngine\Enums\ImapSpecialUse; +use DirectoryTree\ImapEngine\Exceptions\ImapCapabilityException; +use DirectoryTree\ImapEngine\FolderData; use DirectoryTree\ImapEngine\Mailbox; test('it requests special-use attributes when supported', function () { @@ -23,7 +26,11 @@ $mailbox->connect(new ImapConnection($stream)); - expect($mailbox->folders()->sent()?->path())->toBe('Outgoing'); + $folders = $mailbox->folders() + ->with(FolderData::SpecialUse) + ->get(); + + expect($folders->findBySpecialUse(ImapSpecialUse::Sent)?->path())->toBe('Outgoing'); $stream->assertWritten('TAG3 LIST "" "*" RETURN (SPECIAL-USE)'); }); @@ -33,10 +40,8 @@ $stream->feed([ '* OK Welcome to IMAP', 'TAG1 OK Logged in', - '* CAPABILITY IMAP4rev1', - 'TAG2 OK CAPABILITY completed', '* LIST (\\HasNoChildren) "/" "Sent Items"', - 'TAG3 OK LIST completed', + 'TAG2 OK LIST completed', ]); $mailbox = Mailbox::make([ @@ -47,10 +52,12 @@ $mailbox->connect(new ImapConnection($stream)); - expect($mailbox->folders()->sent())->toBeNull(); + $folders = $mailbox->folders()->get(); - $stream->assertWritten('TAG3 LIST "" "*"'); - $stream->assertNotWritten('TAG3 LIST "" "*" RETURN (SPECIAL-USE)'); + expect($folders->findBySpecialUse(ImapSpecialUse::Sent))->toBeNull(); + + $stream->assertWritten('TAG2 LIST "" "*"'); + $stream->assertNotWritten('TAG2 LIST "" "*" RETURN (SPECIAL-USE)'); }); test('it resolves special-use attributes from an ordinary list response', function () { @@ -58,10 +65,8 @@ $stream->feed([ '* OK Welcome to IMAP', 'TAG1 OK Logged in', - '* CAPABILITY IMAP4rev1', - 'TAG2 OK CAPABILITY completed', '* LIST (\\Sent \\HasNoChildren) "/" "Outgoing"', - 'TAG3 OK LIST completed', + 'TAG2 OK LIST completed', ]); $mailbox = Mailbox::make([ @@ -72,9 +77,11 @@ $mailbox->connect(new ImapConnection($stream)); - expect($mailbox->folders()->sent()?->path())->toBe('Outgoing'); + $folders = $mailbox->folders()->get(); - $stream->assertWritten('TAG3 LIST "" "*"'); + expect($folders->findBySpecialUse(ImapSpecialUse::Sent)?->path())->toBe('Outgoing'); + + $stream->assertWritten('TAG2 LIST "" "*"'); }); test('it resolves special-use attributes instead of matching folder names', function () { @@ -97,18 +104,42 @@ $mailbox->connect(new ImapConnection($stream)); - expect($mailbox->folders()->sent()?->path())->toBe('Outgoing'); + $folders = $mailbox->folders() + ->with(FolderData::SpecialUse) + ->get(); + + expect($folders->findBySpecialUse(ImapSpecialUse::Sent)?->path())->toBe('Outgoing'); }); test('it returns null when a special-use folder cannot be resolved', function () { + $stream = new FakeStream; + $stream->feed([ + '* OK Welcome to IMAP', + 'TAG1 OK Logged in', + '* LIST (\\HasNoChildren) "/" "INBOX"', + 'TAG2 OK LIST completed', + ]); + + $mailbox = Mailbox::make([ + 'host' => 'imap.example.com', + 'username' => 'foo', + 'password' => 'bar', + ]); + + $mailbox->connect(new ImapConnection($stream)); + + $folders = $mailbox->folders()->get(); + + expect($folders->findBySpecialUse(ImapSpecialUse::Archive))->toBeNull(); +}); + +test('it throws when requested folder data is not supported', function () { $stream = new FakeStream; $stream->feed([ '* OK Welcome to IMAP', 'TAG1 OK Logged in', '* CAPABILITY IMAP4rev1', 'TAG2 OK CAPABILITY completed', - '* LIST (\\HasNoChildren) "/" "INBOX"', - 'TAG3 OK LIST completed', ]); $mailbox = Mailbox::make([ @@ -119,5 +150,8 @@ $mailbox->connect(new ImapConnection($stream)); - expect($mailbox->folders()->archive())->toBeNull(); + expect(fn () => $mailbox->folders() + ->with(FolderData::SpecialUse) + ->get() + )->toThrow(ImapCapabilityException::class); }); diff --git a/tests/Unit/Testing/FakeFolderRepositoryTest.php b/tests/Unit/Testing/FakeFolderRepositoryTest.php index ee8edc7..a1a6b71 100644 --- a/tests/Unit/Testing/FakeFolderRepositoryTest.php +++ b/tests/Unit/Testing/FakeFolderRepositoryTest.php @@ -2,6 +2,7 @@ use DirectoryTree\ImapEngine\Collections\FolderCollection; use DirectoryTree\ImapEngine\Enums\ImapSpecialUse; +use DirectoryTree\ImapEngine\FolderData; use DirectoryTree\ImapEngine\Testing\FakeFolder; use DirectoryTree\ImapEngine\Testing\FakeFolderRepository; use DirectoryTree\ImapEngine\Testing\FakeMailbox; @@ -101,7 +102,7 @@ expect($noMatches)->toBeEmpty(); }); -test('it resolves special-use folders', function () { +test('it resolves special-use folders from the collection', function () { $mailbox = new FakeMailbox; $sentByName = new FakeFolder('Sent'); $sentByAttribute = new FakeFolder('Outgoing', [ImapSpecialUse::Sent->value]); @@ -117,11 +118,15 @@ new FakeFolder('[Gmail]/All Mail', [ImapSpecialUse::All->value]), ]); - expect($repository->sent())->toBe($sentByAttribute); - expect($repository->drafts()?->name())->toBe('Drafts'); - expect($repository->flagged()?->name())->toBe('Starred'); - expect($repository->junk()?->name())->toBe('Junk Email'); - expect($repository->trash()?->name())->toBe('Deleted Items'); - expect($repository->archive()?->name())->toBe('Archive'); - expect($repository->allMail()?->name())->toBe('All Mail'); + $folders = $repository + ->with(FolderData::SpecialUse) + ->get(); + + expect($folders->findBySpecialUse(ImapSpecialUse::Sent))->toBe($sentByAttribute); + expect($folders->findBySpecialUse(ImapSpecialUse::Drafts)?->name())->toBe('Drafts'); + expect($folders->findBySpecialUse(ImapSpecialUse::Flagged)?->name())->toBe('Starred'); + expect($folders->findBySpecialUse(ImapSpecialUse::Junk)?->name())->toBe('Junk Email'); + expect($folders->findBySpecialUse(ImapSpecialUse::Trash)?->name())->toBe('Deleted Items'); + expect($folders->findBySpecialUse(ImapSpecialUse::Archive)?->name())->toBe('Archive'); + expect($folders->findBySpecialUse(ImapSpecialUse::All)?->name())->toBe('All Mail'); }); From b0f8ec00dad618b28dca2d49b7c81caee498915b Mon Sep 17 00:00:00 2001 From: stevebauman <6421846+stevebauman@users.noreply.github.com> Date: Wed, 2 Sep 2026 16:51:32 +0000 Subject: [PATCH 4/6] Fix code style --- src/FolderRepositoryInterface.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/FolderRepositoryInterface.php b/src/FolderRepositoryInterface.php index e01c514..25b4ea6 100644 --- a/src/FolderRepositoryInterface.php +++ b/src/FolderRepositoryInterface.php @@ -3,6 +3,7 @@ namespace DirectoryTree\ImapEngine; use DirectoryTree\ImapEngine\Collections\FolderCollection; + interface FolderRepositoryInterface { /** From d28e218a706fa1b34baead16c3e6db55a5eee945 Mon Sep 17 00:00:00 2001 From: Steve Bauman Date: Wed, 2 Sep 2026 13:11:33 -0400 Subject: [PATCH 5/6] Keep special-use resolution on folder collections --- src/Collections/FolderCollection.php | 12 ++++++-- src/Folder.php | 2 +- src/FolderInterface.php | 14 ---------- src/HasSpecialUses.php | 42 ---------------------------- src/Testing/FakeFolder.php | 3 +- tests/Unit/FolderRepositoryTest.php | 2 +- tests/Unit/FolderTest.php | 16 ----------- 7 files changed, 12 insertions(+), 79 deletions(-) delete mode 100644 src/HasSpecialUses.php diff --git a/src/Collections/FolderCollection.php b/src/Collections/FolderCollection.php index 8b8aec8..29ca57c 100644 --- a/src/Collections/FolderCollection.php +++ b/src/Collections/FolderCollection.php @@ -16,8 +16,14 @@ class FolderCollection extends Collection */ public function findBySpecialUse(ImapSpecialUse $specialUse): ?FolderInterface { - return $this->first( - fn (FolderInterface $folder) => $folder->hasSpecialUse($specialUse) - ); + return $this->first(function (FolderInterface $folder) use ($specialUse) { + foreach ($folder->flags() as $flag) { + if (strcasecmp($flag, $specialUse->value) === 0) { + return true; + } + } + + return false; + }); } } diff --git a/src/Folder.php b/src/Folder.php index b578aa5..0646fe1 100644 --- a/src/Folder.php +++ b/src/Folder.php @@ -15,7 +15,7 @@ class Folder implements Arrayable, FolderInterface, JsonSerializable { - use ComparesFolders, HasSpecialUses; + use ComparesFolders; /** * Constructor. diff --git a/src/FolderInterface.php b/src/FolderInterface.php index c0321fc..9958dd2 100644 --- a/src/FolderInterface.php +++ b/src/FolderInterface.php @@ -2,8 +2,6 @@ namespace DirectoryTree\ImapEngine; -use DirectoryTree\ImapEngine\Enums\ImapSpecialUse; - interface FolderInterface { /** @@ -23,18 +21,6 @@ public function path(): string; */ public function flags(): array; - /** - * Get the folder's special uses. - * - * @return ImapSpecialUse[] - */ - public function specialUses(): array; - - /** - * Determine if the folder has the given special use. - */ - public function hasSpecialUse(ImapSpecialUse $specialUse): bool; - /** * Get the folder delimiter. */ diff --git a/src/HasSpecialUses.php b/src/HasSpecialUses.php deleted file mode 100644 index 197e05f..0000000 --- a/src/HasSpecialUses.php +++ /dev/null @@ -1,42 +0,0 @@ - $this->hasSpecialUse($specialUse) - )); - } - - /** - * Determine if the folder has the given special use. - */ - public function hasSpecialUse(ImapSpecialUse $specialUse): bool - { - foreach ($this->flags() as $flag) { - if (strcasecmp($flag, $specialUse->value) === 0) { - return true; - } - } - - return false; - } - - /** - * Get the folder's flags. - * - * @return string[] - */ - abstract public function flags(): array; -} diff --git a/src/Testing/FakeFolder.php b/src/Testing/FakeFolder.php index fb25af2..45d14d1 100644 --- a/src/Testing/FakeFolder.php +++ b/src/Testing/FakeFolder.php @@ -5,14 +5,13 @@ use DirectoryTree\ImapEngine\ComparesFolders; use DirectoryTree\ImapEngine\Exceptions\Exception; use DirectoryTree\ImapEngine\FolderInterface; -use DirectoryTree\ImapEngine\HasSpecialUses; use DirectoryTree\ImapEngine\MailboxInterface; use DirectoryTree\ImapEngine\MessageQueryInterface; use DirectoryTree\ImapEngine\Support\Str; class FakeFolder implements FolderInterface { - use ComparesFolders, HasSpecialUses; + use ComparesFolders; /** * Constructor. diff --git a/tests/Unit/FolderRepositoryTest.php b/tests/Unit/FolderRepositoryTest.php index f8d6b38..d51206c 100644 --- a/tests/Unit/FolderRepositoryTest.php +++ b/tests/Unit/FolderRepositoryTest.php @@ -14,7 +14,7 @@ 'TAG1 OK Logged in', '* CAPABILITY IMAP4rev1 SPECIAL-USE', 'TAG2 OK CAPABILITY completed', - '* LIST (\\Sent \\HasNoChildren) "/" "Outgoing"', + '* LIST (\\sent \\HasNoChildren) "/" "Outgoing"', 'TAG3 OK LIST completed', ]); diff --git a/tests/Unit/FolderTest.php b/tests/Unit/FolderTest.php index 326cf37..ebdf8f1 100644 --- a/tests/Unit/FolderTest.php +++ b/tests/Unit/FolderTest.php @@ -1,7 +1,6 @@ name())->toBe($mixedUtf8FolderName); }); -test('it resolves special uses from folder flags', function () { - $folder = new Folder( - mailbox: Mailbox::make(), - path: 'Sent', - flags: ['\\HasNoChildren', '\\sent', '\\Archive'], - ); - - expect($folder->specialUses())->toBe([ - ImapSpecialUse::Archive, - ImapSpecialUse::Sent, - ]); - expect($folder->hasSpecialUse(ImapSpecialUse::Sent))->toBeTrue(); - expect($folder->hasSpecialUse(ImapSpecialUse::Trash))->toBeFalse(); -}); - test('it returns quota data for the mailbox', function () { $mailbox = Mailbox::make([ 'username' => 'foo', From 891f54ae6bfa53bc7f96d61a1094e8b852196c5e Mon Sep 17 00:00:00 2001 From: Steve Bauman Date: Wed, 2 Sep 2026 13:21:44 -0400 Subject: [PATCH 6/6] Rename folder flags to attributes --- src/Collections/FolderCollection.php | 4 ++-- src/Folder.php | 10 +++++----- src/FolderInterface.php | 4 ++-- src/FolderRepository.php | 2 +- src/Testing/FakeFolder.php | 12 ++++++------ tests/Unit/FolderTest.php | 6 +++--- tests/Unit/MailboxTest.php | 4 ++-- tests/Unit/Testing/FakeFolderTest.php | 8 ++++---- 8 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/Collections/FolderCollection.php b/src/Collections/FolderCollection.php index 29ca57c..732bff2 100644 --- a/src/Collections/FolderCollection.php +++ b/src/Collections/FolderCollection.php @@ -17,8 +17,8 @@ class FolderCollection extends Collection public function findBySpecialUse(ImapSpecialUse $specialUse): ?FolderInterface { return $this->first(function (FolderInterface $folder) use ($specialUse) { - foreach ($folder->flags() as $flag) { - if (strcasecmp($flag, $specialUse->value) === 0) { + foreach ($folder->attributes() as $attribute) { + if (strcasecmp($attribute, $specialUse->value) === 0) { return true; } } diff --git a/src/Folder.php b/src/Folder.php index 0646fe1..a05f328 100644 --- a/src/Folder.php +++ b/src/Folder.php @@ -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 = '/', ) {} @@ -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; } /** @@ -263,7 +263,7 @@ public function toArray(): array { return [ 'path' => $this->path, - 'flags' => $this->flags, + 'attributes' => $this->attributes, 'delimiter' => $this->delimiter, ]; } diff --git a/src/FolderInterface.php b/src/FolderInterface.php index 9958dd2..1688d53 100644 --- a/src/FolderInterface.php +++ b/src/FolderInterface.php @@ -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. diff --git a/src/FolderRepository.php b/src/FolderRepository.php index 81be96c..49032c2 100644 --- a/src/FolderRepository.php +++ b/src/FolderRepository.php @@ -90,7 +90,7 @@ public function get(?string $match = '*', ?string $reference = ''): FolderCollec 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); diff --git a/src/Testing/FakeFolder.php b/src/Testing/FakeFolder.php index 45d14d1..d1e9d21 100644 --- a/src/Testing/FakeFolder.php +++ b/src/Testing/FakeFolder.php @@ -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 = '/', @@ -44,9 +44,9 @@ public function path(): string /** * {@inheritDoc} */ - public function flags(): array + public function attributes(): array { - return $this->flags; + return $this->attributes; } /** @@ -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; } diff --git a/tests/Unit/FolderTest.php b/tests/Unit/FolderTest.php index ebdf8f1..cc1e483 100644 --- a/tests/Unit/FolderTest.php +++ b/tests/Unit/FolderTest.php @@ -12,7 +12,7 @@ $folder = new Folder( mailbox: $mailbox, path: '[Gmail]/&BBoEPgRABDcEOAQ9BDA-', - flags: ['\\HasNoChildren'], + attributes: ['\\HasNoChildren'], delimiter: '/' ); @@ -32,7 +32,7 @@ $folder = new Folder( mailbox: $mailbox, path: '[Gmail]/'.$utf8FolderName, - flags: ['\\HasNoChildren'], + attributes: ['\\HasNoChildren'], delimiter: '/' ); @@ -45,7 +45,7 @@ $mixedFolder = new Folder( mailbox: $mailbox, path: '[Gmail]/'.$mixedUtf8FolderName, - flags: ['\\HasNoChildren'], + attributes: ['\\HasNoChildren'], delimiter: '/' ); diff --git a/tests/Unit/MailboxTest.php b/tests/Unit/MailboxTest.php index 09b6448..ab03302 100644 --- a/tests/Unit/MailboxTest.php +++ b/tests/Unit/MailboxTest.php @@ -113,7 +113,7 @@ expect($folders)->toHaveCount(1); expect($folders[0]->path())->toBe('INBOX'); - expect($folders[0]->flags())->toBe(['\\HasNoChildren']); + expect($folders[0]->attributes())->toBe(['\\HasNoChildren']); }); test('inbox', function () { @@ -131,7 +131,7 @@ expect($folder)->toBeInstanceOf(Folder::class); expect($folder->path())->toBe('INBOX'); - expect($folder->flags())->toBe(['\\HasNoChildren']); + expect($folder->attributes())->toBe(['\\HasNoChildren']); }); test('capabilities', function () { diff --git a/tests/Unit/Testing/FakeFolderTest.php b/tests/Unit/Testing/FakeFolderTest.php index 0a1d1e9..9a3a022 100644 --- a/tests/Unit/Testing/FakeFolderTest.php +++ b/tests/Unit/Testing/FakeFolderTest.php @@ -16,7 +16,7 @@ expect($folder)->toBeInstanceOf(FakeFolder::class); expect($folder->path())->toBe('INBOX'); - expect($folder->flags())->toBe(['\\HasNoChildren']); + expect($folder->attributes())->toBe(['\\HasNoChildren']); expect($folder->delimiter())->toBe('/'); }); @@ -61,12 +61,12 @@ expect($folder->path())->toBe('Sent'); }); -test('it can set flags', function () { +test('it can set attributes', function () { $folder = new FakeFolder('INBOX'); - $folder->setFlags(['\\Seen', '\\HasNoChildren']); + $folder->setAttributes(['\\Seen', '\\HasNoChildren']); - expect($folder->flags())->toBe(['\\Seen', '\\HasNoChildren']); + expect($folder->attributes())->toBe(['\\Seen', '\\HasNoChildren']); }); test('it can set mailbox', function () {