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
18 changes: 18 additions & 0 deletions src/Connection/ConnectionInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -313,4 +313,22 @@ public function subscribe(string $folder): TaggedResponse;
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-unsubscribe-command
*/
public function unsubscribe(string $folder): TaggedResponse;

/**
* Send a "GETQUOTA" command.
*
* Retrieve quota information about a specific quota root.
*
* @see https://datatracker.ietf.org/doc/html/rfc9208#name-getquota
*/
public function quota(string $root): UntaggedResponse;

/**
* Send a "GETQUOTAROOT" command.
*
* Retrieve quota root information about a mailbox.
*
* @see https://datatracker.ietf.org/doc/html/rfc9208#name-getquotaroot
*/
public function quotaRoot(string $mailbox): ResponseCollection;
}
28 changes: 28 additions & 0 deletions src/Connection/ImapConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,34 @@ public function unsubscribe(string $folder): TaggedResponse
return $this->assertTaggedResponse($tag);
}

/**
* {@inheritDoc}
*/
public function quota(string $root): UntaggedResponse
{
$this->send('GETQUOTA', [Str::literal($root)], tag: $tag);

$this->assertTaggedResponse($tag);

return $this->result->responses()->untagged()->firstOrFail(
fn (UntaggedResponse $response) => $response->type()->is('QUOTA')
);
}

/**
* {@inheritDoc}
*/
public function quotaRoot(string $mailbox): ResponseCollection
{
$this->send('GETQUOTAROOT', [Str::literal($mailbox)], tag: $tag);

$this->assertTaggedResponse($tag);

return $this->result->responses()->untagged()->filter(
fn (UntaggedResponse $response) => $response->type()->is('QUOTA')
);
}

/**
* {@inheritDoc}
*/
Expand Down
31 changes: 31 additions & 0 deletions src/Folder.php
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,37 @@ public function select(bool $force = false): void
$this->mailbox->select($this, $force);
}

/**
* {@inheritDoc}
*/
public function quota(): array
{
if (! in_array('QUOTA', $this->mailbox->capabilities())) {
throw new ImapCapabilityException(
'Unable to fetch mailbox quotas. IMAP server does not support QUOTA capability.'
);
}

$responses = $this->mailbox->connection()->quotaRoot($this->path);

$values = [];

foreach ($responses as $response) {
$resource = $response->tokenAt(2);

$tokens = $response->tokenAt(3)->tokens();

for ($i = 0; $i + 2 < count($tokens); $i += 3) {
$values[$resource->value][$tokens[$i]->value] = [
'usage' => (int) $tokens[$i + 1]->value,
'limit' => (int) $tokens[$i + 2]->value,
];
}
}

return $values;
}

/**
* {@inheritDoc}
*/
Expand Down
5 changes: 5 additions & 0 deletions src/FolderInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ public function move(string $newPath): void;
*/
public function select(bool $force = false): void;

/**
* Get the folder's quotas.
*/
public function quota(): array;

/**
* Get the folder's status.
*/
Expand Down
19 changes: 19 additions & 0 deletions src/Testing/FakeFolder.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,25 @@ public function expunge(): array
return [];
}

/**
* {@inheritDoc}
*/
public function quota(): array
{
return [
$this->path => [
'STORAGE' => [
'usage' => 0,
'limit' => 0,
],
'MESSAGE' => [
'usage' => 0,
'limit' => 0,
],
],
];
}

/**
* {@inheritDoc}
*/
Expand Down
6 changes: 6 additions & 0 deletions tests/Integration/FoldersTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,9 @@

expect($mailbox->folders()->find('foo'))->toBeNull();
});

test('quota', function () {
$folder = mailbox()->inbox();

expect($folder->quota())->toBeArray();
});
163 changes: 163 additions & 0 deletions tests/Unit/FolderTest.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

use DirectoryTree\ImapEngine\Connection\ImapConnection;
use DirectoryTree\ImapEngine\Exceptions\ImapCapabilityException;
use DirectoryTree\ImapEngine\Folder;
use DirectoryTree\ImapEngine\Mailbox;

Expand Down Expand Up @@ -50,3 +52,164 @@
// The name should remain unchanged.
expect($mixedFolder->name())->toBe($mixedUtf8FolderName);
});

test('it returns quota data for the mailbox', function () {
$mailbox = Mailbox::make([
'username' => 'foo',
'password' => 'bar',
]);

$mailbox->connect(ImapConnection::fake([
'* OK Welcome to IMAP',
'TAG1 OK Logged in',
'* LIST (\\HasNoChildren) "/" "INBOX"',
'TAG2 OK LIST completed',
'* CAPABILITY IMAP4rev1 LITERAL+ UIDPLUS SORT IDLE MOVE QUOTA',
'TAG3 OK CAPABILITY completed',
'* QUOTA "INBOX" (STORAGE 54 512)',
'* QUOTA "INBOX" (MESSAGE 12 1024)',
'TAG4 OK GETQUOTAROOT completed',
]));

expect($mailbox->inbox()->quota())
->toBeArray()
->toMatchArray([
'INBOX' => [
'STORAGE' => [
'usage' => 54,
'limit' => 512,
],
'MESSAGE' => [
'usage' => 12,
'limit' => 1024,
],
],
]);
});

test('it returns quota data for the mailbox when there are no quotas', function () {
$mailbox = Mailbox::make([
'username' => 'foo',
'password' => 'bar',
]);

$mailbox->connect(ImapConnection::fake([
'* OK Welcome to IMAP',
'TAG1 OK Logged in',
'* LIST (\\HasNoChildren) "/" "INBOX"',
'TAG2 OK LIST completed',
'* CAPABILITY IMAP4rev1 LITERAL+ UIDPLUS SORT IDLE MOVE QUOTA',
'TAG3 OK CAPABILITY completed',
'TAG4 OK GETQUOTAROOT completed',
]));

expect($mailbox->inbox()->quota())->toBe([]);
});

test('it returns quota data for the mailbox when there are multiple resources', function () {
$mailbox = Mailbox::make([
'username' => 'foo',
'password' => 'bar',
]);

$mailbox->connect(ImapConnection::fake([
'* OK Welcome to IMAP',
'TAG1 OK Logged in',
'* LIST (\\HasNoChildren) "/" "INBOX"',
'TAG2 OK LIST completed',
'* CAPABILITY IMAP4rev1 LITERAL+ UIDPLUS SORT IDLE MOVE QUOTA',
'TAG3 OK CAPABILITY completed',
'* QUOTA "FOO" (STORAGE 54 512)',
'* QUOTA "FOO" (MESSAGE 12 1024)',
'* QUOTA "BAR" (STORAGE 10 1024)',
'* QUOTA "BAR" (MESSAGE 5 1024)',
'TAG4 OK GETQUOTAROOT completed',
]));

expect($mailbox->inbox()->quota())
->toBeArray()
->toMatchArray([
'FOO' => [
'STORAGE' => [
'usage' => 54,
'limit' => 512,
],
'MESSAGE' => [
'usage' => 12,
'limit' => 1024,
],
],
'BAR' => [
'STORAGE' => [
'usage' => 10,
'limit' => 1024,
],
'MESSAGE' => [
'usage' => 5,
'limit' => 1024,
],
],
]);
});

test('it returns quota data for the mailbox when there are multiple resources in the same list data', function () {
$mailbox = Mailbox::make([
'username' => 'foo',
'password' => 'bar',
]);

$mailbox->connect(ImapConnection::fake([
'* OK Welcome to IMAP',
'TAG1 OK Logged in',
'* LIST (\\HasNoChildren) "/" "INBOX"',
'TAG2 OK LIST completed',
'* CAPABILITY IMAP4rev1 LITERAL+ UIDPLUS SORT IDLE MOVE QUOTA',
'TAG3 OK CAPABILITY completed',
'* QUOTA "FOO" (STORAGE 54 512 MESSAGE 12 1024)',
'* QUOTA "BAR" (STORAGE 10 1024 MESSAGE 5 1024)',
'TAG4 OK GETQUOTAROOT completed',
]));

expect($mailbox->inbox()->quota())
->toBeArray()
->toMatchArray([
'FOO' => [
'STORAGE' => [
'usage' => 54,
'limit' => 512,
],
'MESSAGE' => [
'usage' => 12,
'limit' => 1024,
],
],
'BAR' => [
'STORAGE' => [
'usage' => 10,
'limit' => 1024,
],
'MESSAGE' => [
'usage' => 5,
'limit' => 1024,
],
],
]);
});

test('it throws an imap capability exception when inspecting quotas when the imap server does not support quotas', function () {
$mailbox = Mailbox::make([
'username' => 'foo',
'password' => 'bar',
]);

$mailbox->connect(ImapConnection::fake([
'* OK Welcome to IMAP',
'TAG1 OK Logged in',
'* LIST (\\HasNoChildren) "/" "INBOX"',
'TAG2 OK LIST completed',
'* CAPABILITY IMAP4rev1 LITERAL+ UIDPLUS SORT IDLE MOVE',
'TAG3 OK CAPABILITY completed',
]));

$mailbox->inbox()->quota();
})->throws(ImapCapabilityException::class);
17 changes: 17 additions & 0 deletions tests/Unit/Testing/FakeFolderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,20 @@
expect($folder->messages()->where('Unseen')->count())->toBe(3);
expect($folder->messages()->where('Seen')->count())->toBe(3);
});

test('it returns stub quota values', function () {
$folder = new FakeFolder('INBOX');

expect($folder->quota())->toBe([
'INBOX' => [
'STORAGE' => [
'usage' => 0,
'limit' => 0,
],
'MESSAGE' => [
'usage' => 0,
'limit' => 0,
],
],
]);
});