Skip to content

Commit 29c8884

Browse files
authored
Merge pull request #107 from Sn0wCrack/feature/quotas
Basic support for fetching Folder and Mailbox Quotas
2 parents 442c409 + 5218ce2 commit 29c8884

File tree

8 files changed

+287
-0
lines changed

8 files changed

+287
-0
lines changed

src/Connection/ConnectionInterface.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,4 +313,22 @@ public function subscribe(string $folder): TaggedResponse;
313313
* @see https://datatracker.ietf.org/doc/html/rfc9051#name-unsubscribe-command
314314
*/
315315
public function unsubscribe(string $folder): TaggedResponse;
316+
317+
/**
318+
* Send a "GETQUOTA" command.
319+
*
320+
* Retrieve quota information about a specific quota root.
321+
*
322+
* @see https://datatracker.ietf.org/doc/html/rfc9208#name-getquota
323+
*/
324+
public function quota(string $root): UntaggedResponse;
325+
326+
/**
327+
* Send a "GETQUOTAROOT" command.
328+
*
329+
* Retrieve quota root information about a mailbox.
330+
*
331+
* @see https://datatracker.ietf.org/doc/html/rfc9208#name-getquotaroot
332+
*/
333+
public function quotaRoot(string $mailbox): ResponseCollection;
316334
}

src/Connection/ImapConnection.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,34 @@ public function unsubscribe(string $folder): TaggedResponse
314314
return $this->assertTaggedResponse($tag);
315315
}
316316

317+
/**
318+
* {@inheritDoc}
319+
*/
320+
public function quota(string $root): UntaggedResponse
321+
{
322+
$this->send('GETQUOTA', [Str::literal($root)], tag: $tag);
323+
324+
$this->assertTaggedResponse($tag);
325+
326+
return $this->result->responses()->untagged()->firstOrFail(
327+
fn (UntaggedResponse $response) => $response->type()->is('QUOTA')
328+
);
329+
}
330+
331+
/**
332+
* {@inheritDoc}
333+
*/
334+
public function quotaRoot(string $mailbox): ResponseCollection
335+
{
336+
$this->send('GETQUOTAROOT', [Str::literal($mailbox)], tag: $tag);
337+
338+
$this->assertTaggedResponse($tag);
339+
340+
return $this->result->responses()->untagged()->filter(
341+
fn (UntaggedResponse $response) => $response->type()->is('QUOTA')
342+
);
343+
}
344+
317345
/**
318346
* {@inheritDoc}
319347
*/

src/Folder.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,37 @@ public function select(bool $force = false): void
155155
$this->mailbox->select($this, $force);
156156
}
157157

158+
/**
159+
* {@inheritDoc}
160+
*/
161+
public function quota(): array
162+
{
163+
if (! in_array('QUOTA', $this->mailbox->capabilities())) {
164+
throw new ImapCapabilityException(
165+
'Unable to fetch mailbox quotas. IMAP server does not support QUOTA capability.'
166+
);
167+
}
168+
169+
$responses = $this->mailbox->connection()->quotaRoot($this->path);
170+
171+
$values = [];
172+
173+
foreach ($responses as $response) {
174+
$resource = $response->tokenAt(2);
175+
176+
$tokens = $response->tokenAt(3)->tokens();
177+
178+
for ($i = 0; $i + 2 < count($tokens); $i += 3) {
179+
$values[$resource->value][$tokens[$i]->value] = [
180+
'usage' => (int) $tokens[$i + 1]->value,
181+
'limit' => (int) $tokens[$i + 2]->value,
182+
];
183+
}
184+
}
185+
186+
return $values;
187+
}
188+
158189
/**
159190
* {@inheritDoc}
160191
*/

src/FolderInterface.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ public function move(string $newPath): void;
5656
*/
5757
public function select(bool $force = false): void;
5858

59+
/**
60+
* Get the folder's quotas.
61+
*/
62+
public function quota(): array;
63+
5964
/**
6065
* Get the folder's status.
6166
*/

src/Testing/FakeFolder.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,25 @@ public function expunge(): array
136136
return [];
137137
}
138138

139+
/**
140+
* {@inheritDoc}
141+
*/
142+
public function quota(): array
143+
{
144+
return [
145+
$this->path => [
146+
'STORAGE' => [
147+
'usage' => 0,
148+
'limit' => 0,
149+
],
150+
'MESSAGE' => [
151+
'usage' => 0,
152+
'limit' => 0,
153+
],
154+
],
155+
];
156+
}
157+
139158
/**
140159
* {@inheritDoc}
141160
*/

tests/Integration/FoldersTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,9 @@
102102

103103
expect($mailbox->folders()->find('foo'))->toBeNull();
104104
});
105+
106+
test('quota', function () {
107+
$folder = mailbox()->inbox();
108+
109+
expect($folder->quota())->toBeArray();
110+
});

tests/Unit/FolderTest.php

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
22

3+
use DirectoryTree\ImapEngine\Connection\ImapConnection;
4+
use DirectoryTree\ImapEngine\Exceptions\ImapCapabilityException;
35
use DirectoryTree\ImapEngine\Folder;
46
use DirectoryTree\ImapEngine\Mailbox;
57

@@ -50,3 +52,164 @@
5052
// The name should remain unchanged.
5153
expect($mixedFolder->name())->toBe($mixedUtf8FolderName);
5254
});
55+
56+
test('it returns quota data for the mailbox', function () {
57+
$mailbox = Mailbox::make([
58+
'username' => 'foo',
59+
'password' => 'bar',
60+
]);
61+
62+
$mailbox->connect(ImapConnection::fake([
63+
'* OK Welcome to IMAP',
64+
'TAG1 OK Logged in',
65+
'* LIST (\\HasNoChildren) "/" "INBOX"',
66+
'TAG2 OK LIST completed',
67+
'* CAPABILITY IMAP4rev1 LITERAL+ UIDPLUS SORT IDLE MOVE QUOTA',
68+
'TAG3 OK CAPABILITY completed',
69+
'* QUOTA "INBOX" (STORAGE 54 512)',
70+
'* QUOTA "INBOX" (MESSAGE 12 1024)',
71+
'TAG4 OK GETQUOTAROOT completed',
72+
]));
73+
74+
expect($mailbox->inbox()->quota())
75+
->toBeArray()
76+
->toMatchArray([
77+
'INBOX' => [
78+
'STORAGE' => [
79+
'usage' => 54,
80+
'limit' => 512,
81+
],
82+
'MESSAGE' => [
83+
'usage' => 12,
84+
'limit' => 1024,
85+
],
86+
],
87+
]);
88+
});
89+
90+
test('it returns quota data for the mailbox when there are no quotas', function () {
91+
$mailbox = Mailbox::make([
92+
'username' => 'foo',
93+
'password' => 'bar',
94+
]);
95+
96+
$mailbox->connect(ImapConnection::fake([
97+
'* OK Welcome to IMAP',
98+
'TAG1 OK Logged in',
99+
'* LIST (\\HasNoChildren) "/" "INBOX"',
100+
'TAG2 OK LIST completed',
101+
'* CAPABILITY IMAP4rev1 LITERAL+ UIDPLUS SORT IDLE MOVE QUOTA',
102+
'TAG3 OK CAPABILITY completed',
103+
'TAG4 OK GETQUOTAROOT completed',
104+
]));
105+
106+
expect($mailbox->inbox()->quota())->toBe([]);
107+
});
108+
109+
test('it returns quota data for the mailbox when there are multiple resources', function () {
110+
$mailbox = Mailbox::make([
111+
'username' => 'foo',
112+
'password' => 'bar',
113+
]);
114+
115+
$mailbox->connect(ImapConnection::fake([
116+
'* OK Welcome to IMAP',
117+
'TAG1 OK Logged in',
118+
'* LIST (\\HasNoChildren) "/" "INBOX"',
119+
'TAG2 OK LIST completed',
120+
'* CAPABILITY IMAP4rev1 LITERAL+ UIDPLUS SORT IDLE MOVE QUOTA',
121+
'TAG3 OK CAPABILITY completed',
122+
'* QUOTA "FOO" (STORAGE 54 512)',
123+
'* QUOTA "FOO" (MESSAGE 12 1024)',
124+
'* QUOTA "BAR" (STORAGE 10 1024)',
125+
'* QUOTA "BAR" (MESSAGE 5 1024)',
126+
'TAG4 OK GETQUOTAROOT completed',
127+
]));
128+
129+
expect($mailbox->inbox()->quota())
130+
->toBeArray()
131+
->toMatchArray([
132+
'FOO' => [
133+
'STORAGE' => [
134+
'usage' => 54,
135+
'limit' => 512,
136+
],
137+
'MESSAGE' => [
138+
'usage' => 12,
139+
'limit' => 1024,
140+
],
141+
],
142+
'BAR' => [
143+
'STORAGE' => [
144+
'usage' => 10,
145+
'limit' => 1024,
146+
],
147+
'MESSAGE' => [
148+
'usage' => 5,
149+
'limit' => 1024,
150+
],
151+
],
152+
]);
153+
});
154+
155+
test('it returns quota data for the mailbox when there are multiple resources in the same list data', function () {
156+
$mailbox = Mailbox::make([
157+
'username' => 'foo',
158+
'password' => 'bar',
159+
]);
160+
161+
$mailbox->connect(ImapConnection::fake([
162+
'* OK Welcome to IMAP',
163+
'TAG1 OK Logged in',
164+
'* LIST (\\HasNoChildren) "/" "INBOX"',
165+
'TAG2 OK LIST completed',
166+
'* CAPABILITY IMAP4rev1 LITERAL+ UIDPLUS SORT IDLE MOVE QUOTA',
167+
'TAG3 OK CAPABILITY completed',
168+
'* QUOTA "FOO" (STORAGE 54 512 MESSAGE 12 1024)',
169+
'* QUOTA "BAR" (STORAGE 10 1024 MESSAGE 5 1024)',
170+
'TAG4 OK GETQUOTAROOT completed',
171+
]));
172+
173+
expect($mailbox->inbox()->quota())
174+
->toBeArray()
175+
->toMatchArray([
176+
'FOO' => [
177+
'STORAGE' => [
178+
'usage' => 54,
179+
'limit' => 512,
180+
],
181+
'MESSAGE' => [
182+
'usage' => 12,
183+
'limit' => 1024,
184+
],
185+
],
186+
'BAR' => [
187+
'STORAGE' => [
188+
'usage' => 10,
189+
'limit' => 1024,
190+
],
191+
'MESSAGE' => [
192+
'usage' => 5,
193+
'limit' => 1024,
194+
],
195+
],
196+
]);
197+
});
198+
199+
test('it throws an imap capability exception when inspecting quotas when the imap server does not support quotas', function () {
200+
$mailbox = Mailbox::make([
201+
'username' => 'foo',
202+
'password' => 'bar',
203+
]);
204+
205+
$mailbox->connect(ImapConnection::fake([
206+
'* OK Welcome to IMAP',
207+
'TAG1 OK Logged in',
208+
'* LIST (\\HasNoChildren) "/" "INBOX"',
209+
'TAG2 OK LIST completed',
210+
'* CAPABILITY IMAP4rev1 LITERAL+ UIDPLUS SORT IDLE MOVE',
211+
'TAG3 OK CAPABILITY completed',
212+
]));
213+
214+
$mailbox->inbox()->quota();
215+
})->throws(ImapCapabilityException::class);

tests/Unit/Testing/FakeFolderTest.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,20 @@
111111
expect($folder->messages()->where('Unseen')->count())->toBe(3);
112112
expect($folder->messages()->where('Seen')->count())->toBe(3);
113113
});
114+
115+
test('it returns stub quota values', function () {
116+
$folder = new FakeFolder('INBOX');
117+
118+
expect($folder->quota())->toBe([
119+
'INBOX' => [
120+
'STORAGE' => [
121+
'usage' => 0,
122+
'limit' => 0,
123+
],
124+
'MESSAGE' => [
125+
'usage' => 0,
126+
'limit' => 0,
127+
],
128+
],
129+
]);
130+
});

0 commit comments

Comments
 (0)