ImapProtocol   F
last analyzed

Complexity

Total Complexity 192

Size/Duplication

Total Lines 1220
Duplicated Lines 0 %

Importance

Changes 21
Bugs 4 Features 5
Metric Value
wmc 192
eloc 394
c 21
b 4
f 5
dl 0
loc 1220
rs 2

54 Methods

Rating   Name   Duplication   Size   Complexity  
B connect() 0 22 8
A enableStartTls() 0 5 3
A __construct() 0 3 1
A nextTaggedLine() 0 5 1
A assumedNextTaggedLine() 0 3 1
A assumedNextLine() 0 2 1
A __destruct() 0 2 1
A noop() 0 2 1
A logout() 0 12 2
A copyManyMessages() 0 7 1
A done() 0 6 2
A renameFolder() 0 2 1
A sendRequest() 0 20 5
A search() 0 13 3
A connected() 0 2 1
A createFolder() 0 2 1
A moveMessage() 0 5 1
A nextLine() 0 10 6
A getCapabilities() 0 10 3
A login() 0 8 2
A expunge() 0 2 1
A buildSet() 0 6 3
B examineOrSelect() 0 37 10
B authenticate() 0 25 7
A readResponse() 0 21 6
F fetch() 0 95 31
B getUid() 0 25 11
A folders() 0 16 5
A deleteFolder() 0 2 1
A getQuota() 0 5 1
A readLine() 0 10 2
A escapeList() 0 10 3
A headers() 0 3 2
A requestAndResponse() 0 4 1
A ID() 0 11 4
A idle() 0 4 2
A subscribeFolder() 0 2 1
A examineFolder() 0 2 1
A content() 0 3 2
A escapeString() 0 13 4
B store() 0 24 8
A getQuotaRoot() 0 5 1
B overview() 0 17 8
A unsubscribeFolder() 0 2 1
C decodeLine() 0 75 15
A selectFolder() 0 4 1
A getMessageNumber() 0 9 3
A appendMessage() 0 12 3
A copyMessage() 0 5 1
A flags() 0 3 2
A write() 0 5 3
A enableDebug() 0 2 1
A moveManyMessages() 0 7 1
A disableDebug() 0 2 1

How to fix   Complexity   

Complex Class

Complex classes like ImapProtocol often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ImapProtocol, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
* File: ImapProtocol.php
4
* Category: Protocol
5
* Author: M.Goldenbaum
6
* Created: 16.09.20 18:27
7
* Updated: -
8
*
9
* Description:
10
*  -
11
*/
12
13
namespace Webklex\PHPIMAP\Connection\Protocols;
14
15
use Exception;
16
use Webklex\PHPIMAP\Exceptions\AuthFailedException;
17
use Webklex\PHPIMAP\Exceptions\ConnectionFailedException;
18
use Webklex\PHPIMAP\Exceptions\ImapBadRequestException;
19
use Webklex\PHPIMAP\Exceptions\ImapServerErrorException;
20
use Webklex\PHPIMAP\Exceptions\InvalidMessageDateException;
21
use Webklex\PHPIMAP\Exceptions\MessageNotFoundException;
22
use Webklex\PHPIMAP\Exceptions\RuntimeException;
23
use Webklex\PHPIMAP\Header;
24
use Webklex\PHPIMAP\IMAP;
25
26
/**
27
 * Class ImapProtocol
28
 *
29
 * @package Webklex\PHPIMAP\Connection\Protocols
30
 */
31
class ImapProtocol extends Protocol {
32
33
    /**
34
     * Request noun
35
     * @var int
36
     */
37
    protected $noun = 0;
38
39
    /**
40
     * Imap constructor.
41
     * @param bool $cert_validation set to false to skip SSL certificate validation
42
     * @param mixed $encryption Connection encryption method
43
     */
44
    public function __construct(bool $cert_validation = true, $encryption = false) {
45
        $this->setCertValidation($cert_validation);
46
        $this->encryption = $encryption;
47
    }
48
49
    /**
50
     * @throws ImapBadRequestException
51
     * @throws ImapServerErrorException
52
     * @throws RuntimeException
53
     */
54
    public function __destruct() {
55
        $this->logout();
56
    }
57
58
    /**
59
     * Open connection to IMAP server
60
     * @param string $host hostname or IP address of IMAP server
61
     * @param int|null $port of IMAP server, default is 143 and 993 for ssl
62
     *
63
     * @throws ConnectionFailedException
64
     */
65
    public function connect(string $host, $port = null) {
66
        $transport = 'tcp';
67
        $encryption = '';
68
69
        if ($this->encryption) {
70
            $encryption = strtolower($this->encryption);
71
            if (in_array($encryption, ['ssl', 'tls'])) {
72
                $transport = $encryption;
73
                $port = $port === null ? 993 : $port;
74
            }
75
        }
76
        $port = $port === null ? 143 : $port;
77
        try {
78
            $this->stream = $this->createStream($transport, $host, $port, $this->connection_timeout);
79
            if (!$this->assumedNextLine('* OK')) {
80
                throw new ConnectionFailedException('connection refused');
81
            }
82
            if ($encryption == 'starttls') {
83
                $this->enableStartTls();
84
            }
85
        } catch (Exception $e) {
86
            throw new ConnectionFailedException('connection failed', 0, $e);
87
        }
88
    }
89
90
    /**
91
     * Enable tls on the current connection
92
     *
93
     * @throws ConnectionFailedException
94
     * @throws ImapBadRequestException
95
     * @throws ImapServerErrorException
96
     * @throws RuntimeException
97
     */
98
    protected function enableStartTls(){
99
        $response = $this->requestAndResponse('STARTTLS');
100
        $result = $response && stream_socket_enable_crypto($this->stream, true, $this->getCryptoMethod());
0 ignored issues
show
Bug Best Practice introduced by
The expression $response of type array<integer,true> is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
101
        if (!$result) {
102
            throw new ConnectionFailedException('failed to enable TLS');
103
        }
104
    }
105
106
    /**
107
     * Get the next line from stream
108
     *
109
     * @return string next line
110
     * @throws RuntimeException
111
     */
112
    public function nextLine(): string {
113
        $line = "";
114
        while (($next_char = fread($this->stream, 1)) !== false && $next_char !== "\n") {
115
            $line .= $next_char;
116
        }
117
        if ($line === "" && $next_char === false) {
118
            throw new RuntimeException('empty response');
119
        }
120
        if ($this->debug) echo "<< ".$line."\n";
121
        return $line . "\n";
122
    }
123
124
    /**
125
     * Get the next line and check if it starts with a given string
126
     * @param string $start
127
     *
128
     * @return bool
129
     * @throws RuntimeException
130
     */
131
    protected function assumedNextLine(string $start): bool {
132
        return str_starts_with($this->nextLine(), $start);
133
    }
134
135
    /**
136
     * Get the next line and split the tag
137
     * @param string|null $tag reference tag
138
     *
139
     * @return string next line
140
     * @throws RuntimeException
141
     */
142
    protected function nextTaggedLine(&$tag): string {
143
        $line = $this->nextLine();
144
        list($tag, $line) = explode(' ', $line, 2);
145
146
        return $line;
147
    }
148
149
    /**
150
     * Get the next line and check if it contains a given string and split the tag
151
     * @param string $start
152
     * @param $tag
153
     *
154
     * @return bool
155
     * @throws RuntimeException
156
     */
157
    protected function assumedNextTaggedLine(string $start, &$tag): bool {
158
        $line = $this->nextTaggedLine($tag);
159
        return strpos($line, $start) !== false;
160
    }
161
162
    /**
163
     * Split a given line in values. A value is literal of any form or a list
164
     * @param string $line
165
     *
166
     * @return array
167
     * @throws RuntimeException
168
     */
169
    protected function decodeLine(string $line): array {
170
        $tokens = [];
171
        $stack = [];
172
173
        //  replace any trailing <NL> including spaces with a single space
174
        $line = rtrim($line) . ' ';
175
        while (($pos = strpos($line, ' ')) !== false) {
176
            $token = substr($line, 0, $pos);
177
            if (!strlen($token)) {
178
                $line = substr($line, $pos + 1);
179
                continue;
180
            }
181
            while ($token[0] == '(') {
182
                $stack[] = $tokens;
183
                $tokens = [];
184
                $token = substr($token, 1);
185
            }
186
            if ($token[0] == '"') {
187
                if (preg_match('%^\(*"((.|\\\\|\\")*?)" *%', $line, $matches)) {
188
                    $tokens[] = $matches[1];
189
                    $line = substr($line, strlen($matches[0]));
190
                    continue;
191
                }
192
            }
193
            if ($token[0] == '{') {
194
                $endPos = strpos($token, '}');
195
                $chars = substr($token, 1, $endPos - 1);
196
                if (is_numeric($chars)) {
197
                    $token = '';
198
                    while (strlen($token) < $chars) {
199
                        $token .= $this->nextLine();
200
                    }
201
                    $line = '';
202
                    if (strlen($token) > $chars) {
203
                        $line = substr($token, $chars);
0 ignored issues
show
Bug introduced by
$chars of type string is incompatible with the type integer expected by parameter $offset of substr(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

203
                        $line = substr($token, /** @scrutinizer ignore-type */ $chars);
Loading history...
204
                        $token = substr($token, 0, $chars);
0 ignored issues
show
Bug introduced by
$chars of type string is incompatible with the type integer|null expected by parameter $length of substr(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

204
                        $token = substr($token, 0, /** @scrutinizer ignore-type */ $chars);
Loading history...
205
                    } else {
206
                        $line .= $this->nextLine();
207
                    }
208
                    $tokens[] = $token;
209
                    $line = trim($line) . ' ';
210
                    continue;
211
                }
212
            }
213
            if ($stack && $token[strlen($token) - 1] == ')') {
214
                // closing braces are not separated by spaces, so we need to count them
215
                $braces = strlen($token);
216
                $token = rtrim($token, ')');
217
                // only count braces if more than one
218
                $braces -= strlen($token) + 1;
219
                // only add if token had more than just closing braces
220
                if (rtrim($token) != '') {
221
                    $tokens[] = rtrim($token);
222
                }
223
                $token = $tokens;
224
                $tokens = array_pop($stack);
225
                // special handline if more than one closing brace
226
                while ($braces-- > 0) {
227
                    $tokens[] = $token;
228
                    $token = $tokens;
229
                    $tokens = array_pop($stack);
230
                }
231
            }
232
            $tokens[] = $token;
233
            $line = substr($line, $pos + 1);
234
        }
235
236
        // maybe the server forgot to send some closing braces
237
        while ($stack) {
238
            $child = $tokens;
239
            $tokens = array_pop($stack);
240
            $tokens[] = $child;
241
        }
242
243
        return $tokens;
244
    }
245
246
    /**
247
     * Read abd decode a response "line"
248
     * @param array|string $tokens to decode
249
     * @param string $wantedTag targeted tag
250
     * @param bool $dontParse if true only the unparsed line is returned in $tokens
251
     *
252
     * @return bool
253
     * @throws RuntimeException
254
     */
255
    public function readLine(&$tokens = [], string $wantedTag = '*', bool $dontParse = false): bool {
256
        $line = $this->nextTaggedLine($tag); // get next tag
257
        if (!$dontParse) {
258
            $tokens = $this->decodeLine($line);
259
        } else {
260
            $tokens = $line;
261
        }
262
263
        // if tag is wanted tag we might be at the end of a multiline response
264
        return $tag == $wantedTag;
265
    }
266
267
    /**
268
     * Read all lines of response until given tag is found
269
     *
270
     * @param string $tag     request tag
271
     * @param bool $dontParse if true every line is returned unparsed instead of the decoded tokens
272
     *
273
     * @return array
274
     *
275
     * @throws ImapBadRequestException
276
     * @throws ImapServerErrorException
277
     * @throws RuntimeException
278
     */
279
    public function readResponse(string $tag, bool $dontParse = false): array {
280
        $lines = [];
281
        $tokens = null; // define $tokens variable before first use
282
        do {
283
            $readAll = $this->readLine($tokens, $tag, $dontParse);
284
            $lines[] = $tokens;
285
        } while (!$readAll);
286
287
        if ($dontParse) {
288
            // First two chars are still needed for the response code
289
            $tokens = [substr($tokens, 0, 2)];
290
        }
291
292
        // last line has response code
293
        if ($tokens[0] == 'OK') {
294
            return $lines ?: [true];
0 ignored issues
show
introduced by
$lines is an empty array, thus is always false.
Loading history...
295
        } elseif ($tokens[0] == 'NO') {
296
            throw new ImapServerErrorException();
297
        }
298
299
        throw new ImapBadRequestException();
300
    }
301
302
    /**
303
     * Send a new request
304
     * @param string $command
305
     * @param array $tokens additional parameters to command, use escapeString() to prepare
306
     * @param string|null $tag provide a tag otherwise an autogenerated is returned
307
     *
308
     * @throws RuntimeException
309
     */
310
    public function sendRequest(string $command, array $tokens = [], string &$tag = null) {
311
        if (!$tag) {
312
            $this->noun++;
313
            $tag = 'TAG' . $this->noun;
314
        }
315
316
        $line = $tag . ' ' . $command;
317
318
        foreach ($tokens as $token) {
319
            if (is_array($token)) {
320
                $this->write($line . ' ' . $token[0]);
321
                if (!$this->assumedNextLine('+ ')) {
322
                    throw new RuntimeException('failed to send literal string');
323
                }
324
                $line = $token[1];
325
            } else {
326
                $line .= ' ' . $token;
327
            }
328
        }
329
        $this->write($line);
330
    }
331
332
    /**
333
     * Write data to the current stream
334
     * @param string $data
335
     * @return void
336
     * @throws RuntimeException
337
     */
338
    public function write(string $data) {
339
        if ($this->debug) echo ">> ".$data ."\n";
340
341
        if (fwrite($this->stream, $data . "\r\n") === false) {
342
            throw new RuntimeException('failed to write - connection closed?');
343
        }
344
    }
345
346
    /**
347
     * Send a request and get response at once
348
     *
349
     * @param string $command
350
     * @param array $tokens   parameters as in sendRequest()
351
     * @param bool $dontParse if true unparsed lines are returned instead of tokens
352
     *
353
     * @return array response as in readResponse()
354
     *
355
     * @throws ImapBadRequestException
356
     * @throws ImapServerErrorException
357
     * @throws RuntimeException
358
     */
359
    public function requestAndResponse(string $command, array $tokens = [], bool $dontParse = false): array {
360
        $this->sendRequest($command, $tokens, $tag);
361
362
        return $this->readResponse($tag, $dontParse);
363
    }
364
365
    /**
366
     * Escape one or more literals i.e. for sendRequest
367
     * @param string|array $string the literal/-s
368
     *
369
     * @return string|array escape literals, literals with newline ar returned
370
     *                      as array('{size}', 'string');
371
     */
372
    public function escapeString($string) {
373
        if (func_num_args() < 2) {
374
            if (str_contains($string, "\n")) {
375
                return ['{' . strlen($string) . '}', $string];
376
            } else {
377
                return '"' . str_replace(['\\', '"'], ['\\\\', '\\"'], $string) . '"';
378
            }
379
        }
380
        $result = [];
381
        foreach (func_get_args() as $string) {
0 ignored issues
show
introduced by
$string is overwriting one of the parameters of this function.
Loading history...
382
            $result[] = $this->escapeString($string);
383
        }
384
        return $result;
385
    }
386
387
    /**
388
     * Escape a list with literals or lists
389
     * @param array $list list with literals or lists as PHP array
390
     *
391
     * @return string escaped list for imap
392
     */
393
    public function escapeList(array $list): string {
394
        $result = [];
395
        foreach ($list as $v) {
396
            if (!is_array($v)) {
397
                $result[] = $v;
398
                continue;
399
            }
400
            $result[] = $this->escapeList($v);
401
        }
402
        return '(' . implode(' ', $result) . ')';
403
    }
404
405
    /**
406
     * Login to a new session.
407
     *
408
     * @param string $user     username
409
     * @param string $password password
410
     *
411
     * @return array
412
     * @throws AuthFailedException
413
     * @throws ImapBadRequestException
414
     * @throws ImapServerErrorException
415
     */
416
    public function login(string $user, string $password): array {
417
        try {
418
            $command = 'LOGIN';
419
            $params = $this->escapeString($user, $password);
420
421
            return $this->requestAndResponse($command, $params, true);
0 ignored issues
show
Bug introduced by
It seems like $params can also be of type string; however, parameter $tokens of Webklex\PHPIMAP\Connecti...l::requestAndResponse() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

421
            return $this->requestAndResponse($command, /** @scrutinizer ignore-type */ $params, true);
Loading history...
422
        } catch (RuntimeException $e) {
423
            throw new AuthFailedException("failed to authenticate", 0, $e);
424
        }
425
    }
426
427
    /**
428
     * Authenticate your current IMAP session.
429
     * @param string $user username
430
     * @param string $token access token
431
     *
432
     * @return bool
433
     * @throws AuthFailedException
434
     */
435
    public function authenticate(string $user, string $token): bool {
436
        try {
437
            $authenticateParams = ['XOAUTH2', base64_encode("user=$user\1auth=Bearer $token\1\1")];
438
            $this->sendRequest('AUTHENTICATE', $authenticateParams);
439
440
            while (true) {
441
                $response = "";
442
                $is_plus = $this->readLine($response, '+', true);
443
                if ($is_plus) {
444
                    // try to log the challenge somewhere where it can be found
445
                    error_log("got an extra server challenge: $response");
446
                    // respond with an empty response.
447
                    $this->sendRequest('');
448
                } else {
449
                    if (preg_match('/^NO /i', $response) ||
450
                        preg_match('/^BAD /i', $response)) {
451
                        error_log("got failure response: $response");
452
                        return false;
453
                    } else if (preg_match("/^OK /i", $response)) {
454
                        return true;
455
                    }
456
                }
457
            }
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return boolean. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
458
        } catch (RuntimeException $e) {
459
            throw new AuthFailedException("failed to authenticate", 0, $e);
460
        }
461
    }
462
463
    /**
464
     * Logout of imap server
465
     *
466
     * @return array success
467
     *
468
     * @throws ImapBadRequestException
469
     * @throws ImapServerErrorException
470
     * @throws RuntimeException
471
     */
472
    public function logout(): array {
473
        if (!$this->stream) {
474
            throw new RuntimeException('not connected');
475
        }
476
477
        $result = $this->requestAndResponse('LOGOUT', [], true);
478
479
        fclose($this->stream);
480
        $this->stream = null;
481
        $this->uid_cache = null;
482
483
        return $result;
484
    }
485
486
    /**
487
     * Check if the current session is connected
488
     *
489
     * @return bool
490
     */
491
    public function connected(): bool {
492
        return (boolean) $this->stream;
493
    }
494
495
    /**
496
     * Get an array of available capabilities
497
     *
498
     * @return array list of capabilities
499
     *
500
     * @throws ImapBadRequestException
501
     * @throws ImapServerErrorException
502
     * @throws RuntimeException
503
     */
504
    public function getCapabilities(): array {
505
        $response = $this->requestAndResponse('CAPABILITY');
506
507
        if (!$response) return [];
0 ignored issues
show
Bug Best Practice introduced by
The expression $response of type array<integer,true> is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
introduced by
$response is a non-empty array, thus ! $response is always false.
Loading history...
508
509
        $capabilities = [];
510
        foreach ($response as $line) {
511
            $capabilities = array_merge($capabilities, $line);
0 ignored issues
show
Bug introduced by
$line of type true is incompatible with the type array expected by parameter $arrays of array_merge(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

511
            $capabilities = array_merge($capabilities, /** @scrutinizer ignore-type */ $line);
Loading history...
512
        }
513
        return $capabilities;
514
    }
515
516
    /**
517
     * Examine and select have the same response.
518
     * @param string $command can be 'EXAMINE' or 'SELECT'
519
     * @param string $folder target folder
520
     *
521
     * @return bool|array
522
     * @throws RuntimeException
523
     */
524
    public function examineOrSelect(string $command = 'EXAMINE', string $folder = 'INBOX') {
525
        $this->sendRequest($command, [$this->escapeString($folder)], $tag);
526
527
        $result = [];
528
        $tokens = null; // define $tokens variable before first use
529
        while (!$this->readLine($tokens, $tag)) {
530
            if ($tokens[0] == 'FLAGS') {
531
                array_shift($tokens);
532
                $result['flags'] = $tokens;
533
                continue;
534
            }
535
            switch ($tokens[1]) {
536
                case 'EXISTS':
537
                case 'RECENT':
538
                    $result[strtolower($tokens[1])] = (int)$tokens[0];
539
                    break;
540
                case '[UIDVALIDITY':
541
                    $result['uidvalidity'] = (int)$tokens[2];
542
                    break;
543
                case '[UIDNEXT':
544
                    $result['uidnext'] = (int)$tokens[2];
545
                    break;
546
                case '[UNSEEN':
547
                    $result['unseen'] = (int)$tokens[2];
548
                    break;
549
                case '[NONEXISTENT]':
550
                    throw new RuntimeException("folder doesn't exist");
551
                default:
552
                    // ignore
553
                    break;
554
            }
555
        }
556
557
        if ($tokens[0] != 'OK') {
558
            return false;
559
        }
560
        return $result;
561
    }
562
563
    /**
564
     * Change the current folder
565
     * @param string $folder change to this folder
566
     *
567
     * @return bool|array see examineOrSelect()
568
     * @throws RuntimeException
569
     */
570
    public function selectFolder(string $folder = 'INBOX') {
571
        $this->uid_cache = null;
572
573
        return $this->examineOrSelect('SELECT', $folder);
574
    }
575
576
    /**
577
     * Examine a given folder
578
     * @param string $folder examine this folder
579
     *
580
     * @return bool|array see examineOrSelect()
581
     * @throws RuntimeException
582
     */
583
    public function examineFolder(string $folder = 'INBOX') {
584
        return $this->examineOrSelect('EXAMINE', $folder);
585
    }
586
587
    /**
588
     * Fetch one or more items of one or more messages
589
     * @param string|array $items items to fetch [RFC822.HEADER, FLAGS, RFC822.TEXT, etc]
590
     * @param int|array $from message for items or start message if $to !== null
591
     * @param int|null $to if null only one message ($from) is fetched, else it's the
592
     *                             last message, INF means last message available
593
     * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use
594
     * message numbers instead.
595
     *
596
     * @return string|array if only one item of one message is fetched it's returned as string
597
     *                      if items of one message are fetched it's returned as (name => value)
598
     *                      if one item of messages are fetched it's returned as (msgno => value)
599
     *                      if items of messages are fetched it's returned as (msgno => (name => value))
600
     * @throws RuntimeException
601
     */
602
    public function fetch($items, $from, $to = null, $uid = IMAP::ST_UID) {
603
        if (is_array($from)) {
604
            $set = implode(',', $from);
605
        } elseif ($to === null) {
606
            $set = (int)$from;
607
        } elseif ($to === INF) {
0 ignored issues
show
introduced by
The condition $to === Webklex\PHPIMAP\Connection\Protocols\INF is always false.
Loading history...
608
            $set = (int)$from . ':*';
609
        } else {
610
            $set = (int)$from . ':' . (int)$to;
611
        }
612
613
        $items = (array)$items;
614
        $itemList = $this->escapeList($items);
615
616
        $this->sendRequest($this->buildUIDCommand("FETCH", $uid), [$set, $itemList], $tag);
617
        $result = [];
618
        $tokens = null; // define $tokens variable before first use
619
        while (!$this->readLine($tokens, $tag)) {
620
            // ignore other responses
621
            if ($tokens[1] != 'FETCH') {
622
                continue;
623
            }
624
625
            // find array key of UID value; try the last elements, or search for it
626
            if ($uid) {
627
                $count = count($tokens[2]);
628
                if ($tokens[2][$count - 2] == 'UID') {
629
                    $uidKey = $count - 1;
630
                } else if ($tokens[2][0] == 'UID') {
631
                    $uidKey = 1;
632
                } else {
633
                    $found = array_search('UID', $tokens[2]);
634
                    if ($found === false || $found === -1) {
635
                        continue;
636
                    }
637
638
                    $uidKey = $found + 1;
639
                }
640
            }
641
642
            // ignore other messages
643
            if ($to === null && !is_array($from) && ($uid ? $tokens[2][$uidKey] != $from : $tokens[0] != $from)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $uidKey does not seem to be defined for all execution paths leading up to this point.
Loading history...
644
                continue;
645
            }
646
647
            // if we only want one item we return that one directly
648
            if (count($items) == 1) {
649
                if ($tokens[2][0] == $items[0]) {
650
                    $data = $tokens[2][1];
651
                } elseif ($uid && $tokens[2][2] == $items[0]) {
652
                    $data = $tokens[2][3];
653
                } else {
654
                    $expectedResponse = 0;
655
                    // maybe the server send an other field we didn't wanted
656
                    $count = count($tokens[2]);
657
                    // we start with 2, because 0 was already checked
658
                    for ($i = 2; $i < $count; $i += 2) {
659
                        if ($tokens[2][$i] != $items[0]) {
660
                            continue;
661
                        }
662
                        $data = $tokens[2][$i + 1];
663
                        $expectedResponse = 1;
664
                        break;
665
                    }
666
                    if (!$expectedResponse) {
667
                        continue;
668
                    }
669
                }
670
            } else {
671
                $data = [];
672
                while (key($tokens[2]) !== null) {
673
                    $data[current($tokens[2])] = next($tokens[2]);
674
                    next($tokens[2]);
675
                }
676
            }
677
678
            // if we want only one message we can ignore everything else and just return
679
            if ($to === null && !is_array($from) && ($uid ? $tokens[2][$uidKey] == $from : $tokens[0] == $from)) {
680
                // we still need to read all lines
681
                while (!$this->readLine($tokens, $tag))
682
683
                    return $data;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $data does not seem to be defined for all execution paths leading up to this point.
Loading history...
684
            }
685
            if ($uid) {
686
                $result[$tokens[2][$uidKey]] = $data;
687
            }else{
688
                $result[$tokens[0]] = $data;
689
            }
690
        }
691
692
        if ($to === null && !is_array($from)) {
693
            throw new RuntimeException('the single id was not found in response');
694
        }
695
696
        return $result;
697
    }
698
699
    /**
700
     * Fetch message headers
701
     * @param array|int $uids
702
     * @param string $rfc
703
     * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use
704
     * message numbers instead.
705
     *
706
     * @return array
707
     * @throws RuntimeException
708
     */
709
    public function content($uids, string $rfc = "RFC822", $uid = IMAP::ST_UID): array {
710
        $result = $this->fetch(["$rfc.TEXT"], $uids, null, $uid);
711
        return is_array($result) ? $result : [];
0 ignored issues
show
introduced by
The condition is_array($result) is always true.
Loading history...
712
    }
713
714
    /**
715
     * Fetch message headers
716
     * @param array|int $uids
717
     * @param string $rfc
718
     * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use
719
     * message numbers instead.
720
     *
721
     * @return array
722
     * @throws RuntimeException
723
     */
724
    public function headers($uids, string $rfc = "RFC822", $uid = IMAP::ST_UID): array{
725
        $result = $this->fetch(["$rfc.HEADER"], $uids, null, $uid);
726
        return $result === "" ? [] : $result;
0 ignored issues
show
introduced by
The condition $result === '' is always false.
Loading history...
727
    }
728
729
    /**
730
     * Fetch message flags
731
     * @param array|int $uids
732
     * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use
733
     * message numbers instead.
734
     *
735
     * @return array
736
     * @throws RuntimeException
737
     */
738
    public function flags($uids, $uid = IMAP::ST_UID): array {
739
        $result = $this->fetch(["FLAGS"], $uids, null, $uid);
740
        return is_array($result) ? $result : [];
0 ignored issues
show
introduced by
The condition is_array($result) is always true.
Loading history...
741
    }
742
743
    /**
744
     * Get uid for a given id
745
     * @param int|null $id message number
746
     *
747
     * @return array|string message number for given message or all messages as array
748
     * @throws MessageNotFoundException
749
     */
750
    public function getUid($id = null) {
751
        if (!$this->enable_uid_cache || $this->uid_cache === null || ($this->uid_cache && count($this->uid_cache) <= 0)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->uid_cache of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
752
            try {
753
                $this->setUidCache($this->fetch('UID', 1, INF)); // set cache for this folder
0 ignored issues
show
Bug introduced by
Webklex\PHPIMAP\Connection\Protocols\INF of type double is incompatible with the type integer|null expected by parameter $to of Webklex\PHPIMAP\Connecti...s\ImapProtocol::fetch(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

753
                $this->setUidCache($this->fetch('UID', 1, /** @scrutinizer ignore-type */ INF)); // set cache for this folder
Loading history...
754
            } catch (RuntimeException $e) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
755
        }
756
        $uids = $this->uid_cache;
757
758
        if ($id == null) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $id of type integer|null against null; this is ambiguous if the integer can be zero. Consider using a strict comparison === instead.
Loading history...
759
            return $uids;
760
        }
761
762
        foreach ($uids as $k => $v) {
763
            if ($k == $id) {
764
                return $v;
765
            }
766
        }
767
768
        // clear uid cache and run method again
769
        if ($this->enable_uid_cache && $this->uid_cache) {
770
            $this->setUidCache(null);
771
            return $this->getUid($id);
772
        }
773
774
        throw new MessageNotFoundException('unique id not found');
775
    }
776
777
    /**
778
     * Get a message number for a uid
779
     * @param string $id uid
780
     *
781
     * @return int message number
782
     * @throws MessageNotFoundException
783
     */
784
    public function getMessageNumber(string $id): int {
785
        $ids = $this->getUid();
786
        foreach ($ids as $k => $v) {
787
            if ($v == $id) {
788
                return (int)$k;
789
            }
790
        }
791
792
        throw new MessageNotFoundException('message number not found');
793
    }
794
795
    /**
796
     * Get a list of available folders
797
     *
798
     * @param string $reference mailbox reference for list
799
     * @param string $folder    mailbox name match with wildcards
800
     *
801
     * @return array folders that matched $folder as array(name => array('delimiter' => .., 'flags' => ..))
802
     *
803
     * @throws ImapBadRequestException
804
     * @throws ImapServerErrorException
805
     * @throws RuntimeException
806
     */
807
    public function folders(string $reference = '', string $folder = '*'): array {
808
        $result = [];
809
        $list = $this->requestAndResponse('LIST', $this->escapeString($reference, $folder));
0 ignored issues
show
Bug introduced by
It seems like $this->escapeString($reference, $folder) can also be of type string; however, parameter $tokens of Webklex\PHPIMAP\Connecti...l::requestAndResponse() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

809
        $list = $this->requestAndResponse('LIST', /** @scrutinizer ignore-type */ $this->escapeString($reference, $folder));
Loading history...
810
811
        if ($list[0] === true) {
0 ignored issues
show
introduced by
The condition $list[0] === true is always true.
Loading history...
812
            return $result;
813
        }
814
815
        foreach ($list as $item) {
816
            if (count($item) != 4 || $item[0] != 'LIST') {
817
                continue;
818
            }
819
            $result[$item[3]] = ['delimiter' => $item[2], 'flags' => $item[1]];
820
        }
821
822
        return $result;
823
    }
824
825
    /**
826
     * Manage flags
827
     *
828
     * @param array $flags         flags to set, add or remove - see $mode
829
     * @param int $from            message for items or start message if $to !== null
830
     * @param null $to             if null only one message ($from) is fetched, else it's the
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $to is correct as it would always require null to be passed?
Loading history...
831
     *                             last message, INF means last message available
832
     * @param null $mode           '+' to add flags, '-' to remove flags, everything else sets the flags as given
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $mode is correct as it would always require null to be passed?
Loading history...
833
     * @param bool $silent         if false the return values are the new flags for the wanted messages
834
     * @param int $uid             set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use
835
     *                             message numbers instead.
836
     * @param null $item           command used to store a flag
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $item is correct as it would always require null to be passed?
Loading history...
837
     *
838
     * @return array new flags if $silent is false, else true or false depending on success
839
     * @throws ImapBadRequestException
840
     * @throws ImapServerErrorException
841
     * @throws RuntimeException
842
     */
843
    public function store(
844
        array $flags, int $from, $to = null, $mode = null, bool $silent = true, $uid = IMAP::ST_UID, $item = null
845
    ): array {
846
        $flags = $this->escapeList($flags);
847
        $set = $this->buildSet($from, $to);
848
849
        $command = $this->buildUIDCommand("STORE", $uid);
850
        $item = ($mode == '-' ? "-" : "+").($item === null ? "FLAGS" : $item).($silent ? '.SILENT' : "");
0 ignored issues
show
introduced by
The condition $item === null is always true.
Loading history...
851
852
        $response = $this->requestAndResponse($command, [$set, $item, $flags], $silent);
853
854
        if ($silent) {
855
            return $response;
856
        }
857
858
        $result = [];
859
        foreach ($response as $token) {
860
            if ($token[1] != 'FETCH' || $token[2][0] != 'FLAGS') {
861
                continue;
862
            }
863
            $result[$token[0]] = $token[2][1];
864
        }
865
866
        return $result;
867
    }
868
869
    /**
870
     * Append a new message to given folder
871
     *
872
     * @param string $folder  name of target folder
873
     * @param string $message full message content
874
     * @param null $flags     flags for new message
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $flags is correct as it would always require null to be passed?
Loading history...
875
     * @param null $date      date for new message
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $date is correct as it would always require null to be passed?
Loading history...
876
     *
877
     * @return array success
878
     *
879
     * @throws ImapBadRequestException
880
     * @throws ImapServerErrorException
881
     * @throws RuntimeException
882
     */
883
    public function appendMessage(string $folder, string $message, $flags = null, $date = null): array {
884
        $tokens = [];
885
        $tokens[] = $this->escapeString($folder);
886
        if ($flags !== null) {
0 ignored issues
show
introduced by
The condition $flags !== null is always false.
Loading history...
887
            $tokens[] = $this->escapeList($flags);
888
        }
889
        if ($date !== null) {
0 ignored issues
show
introduced by
The condition $date !== null is always false.
Loading history...
890
            $tokens[] = $this->escapeString($date);
891
        }
892
        $tokens[] = $this->escapeString($message);
893
894
        return $this->requestAndResponse('APPEND', $tokens, true);
895
    }
896
897
    /**
898
     * Copy a message set from current folder to another folder
899
     *
900
     * @param string $folder  destination folder
901
     * @param $from
902
     * @param null $to        if null only one message ($from) is fetched, else it's the
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $to is correct as it would always require null to be passed?
Loading history...
903
     *                        last message, INF means last message available
904
     * @param int $uid        set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use
905
     *                        message numbers instead.
906
     *
907
     * @return array success
908
     *
909
     * @throws ImapBadRequestException
910
     * @throws ImapServerErrorException
911
     * @throws RuntimeException
912
     */
913
    public function copyMessage(string $folder, $from, $to = null, $uid = IMAP::ST_UID): array {
914
        $set = $this->buildSet($from, $to);
915
        $command = $this->buildUIDCommand("COPY", $uid);
916
917
        return $this->requestAndResponse($command, [$set, $this->escapeString($folder)], true);
918
    }
919
920
    /**
921
     * Copy multiple messages to the target folder
922
     *
923
     * @param array $messages List of message identifiers
924
     * @param string $folder  Destination folder
925
     * @param int $uid        set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use
926
     *                        message numbers instead.
927
     *
928
     * @return array Tokens if operation successful, false if an error occurred
929
     *
930
     * @throws ImapBadRequestException
931
     * @throws ImapServerErrorException
932
     * @throws RuntimeException
933
     */
934
    public function copyManyMessages(array $messages, string $folder, $uid = IMAP::ST_UID): array {
935
        $command = $this->buildUIDCommand("COPY", $uid);
936
937
        $set = implode(',', $messages);
938
        $tokens = [$set, $this->escapeString($folder)];
939
940
        return $this->requestAndResponse($command, $tokens, true);
941
    }
942
943
    /**
944
     * Move a message set from current folder to another folder
945
     *
946
     * @param string $folder   destination folder
947
     * @param $from
948
     * @param null $to         if null only one message ($from) is fetched, else it's the
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $to is correct as it would always require null to be passed?
Loading history...
949
     *                         last message, INF means last message available
950
     * @param int $uid         set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use
951
     *                         message numbers instead.
952
     *
953
     * @return array success
954
     *
955
     * @throws ImapBadRequestException
956
     * @throws ImapServerErrorException
957
     * @throws RuntimeException
958
     */
959
    public function moveMessage(string $folder, $from, $to = null, $uid = IMAP::ST_UID): array {
960
        $set = $this->buildSet($from, $to);
961
        $command = $this->buildUIDCommand("MOVE", $uid);
962
963
        return $this->requestAndResponse($command, [$set, $this->escapeString($folder)], true);
964
    }
965
966
    /**
967
     * Move multiple messages to the target folder
968
     *
969
     * @param array $messages List of message identifiers
970
     * @param string $folder  Destination folder
971
     * @param int $uid        set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use
972
     *                        message numbers instead.
973
     *
974
     * @return array success
975
     *
976
     * @throws ImapBadRequestException
977
     * @throws ImapServerErrorException
978
     * @throws RuntimeException
979
     */
980
    public function moveManyMessages(array $messages, string $folder, $uid = IMAP::ST_UID): array {
981
        $command = $this->buildUIDCommand("MOVE", $uid);
982
983
        $set = implode(',', $messages);
984
        $tokens = [$set, $this->escapeString($folder)];
985
986
        return $this->requestAndResponse($command, $tokens, true);
987
    }
988
989
    /**
990
     * Exchange identification information
991
     * Ref.: https://datatracker.ietf.org/doc/html/rfc2971
992
     *
993
     * @param array|null $ids
994
     * @return array
995
     *
996
     * @throws ImapBadRequestException
997
     * @throws ImapServerErrorException
998
     * @throws RuntimeException
999
     */
1000
    public function ID($ids = null): array {
1001
        $token = "NIL";
1002
        if (is_array($ids) && !empty($ids)) {
1003
            $token = "(";
1004
            foreach ($ids as $id) {
1005
                $token .= '"'.$id.'" ';
1006
            }
1007
            $token = rtrim($token).")";
1008
        }
1009
1010
        return $this->requestAndResponse("ID", [$token], true);
1011
    }
1012
1013
    /**
1014
     * Create a new folder (and parent folders if needed)
1015
     *
1016
     * @param string $folder folder name
1017
     * @return array success
1018
     *
1019
     * @throws ImapBadRequestException
1020
     * @throws ImapServerErrorException
1021
     * @throws RuntimeException
1022
     */
1023
    public function createFolder(string $folder): array {
1024
        return $this->requestAndResponse('CREATE', [$this->escapeString($folder)], true);
1025
    }
1026
1027
    /**
1028
     * Rename an existing folder
1029
     *
1030
     * @param string $old old name
1031
     * @param string $new new name
1032
     *
1033
     * @return array success
1034
     *
1035
     * @throws ImapBadRequestException
1036
     * @throws ImapServerErrorException
1037
     * @throws RuntimeException
1038
     */
1039
    public function renameFolder(string $old, string $new): array {
1040
        return $this->requestAndResponse('RENAME', $this->escapeString($old, $new), true);
0 ignored issues
show
Bug introduced by
It seems like $this->escapeString($old, $new) can also be of type string; however, parameter $tokens of Webklex\PHPIMAP\Connecti...l::requestAndResponse() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1040
        return $this->requestAndResponse('RENAME', /** @scrutinizer ignore-type */ $this->escapeString($old, $new), true);
Loading history...
1041
    }
1042
1043
    /**
1044
     * Delete a folder
1045
     *
1046
     * @param string $folder folder name
1047
     * @return array success
1048
     *
1049
     * @throws ImapBadRequestException
1050
     * @throws ImapServerErrorException
1051
     * @throws RuntimeException
1052
     */
1053
    public function deleteFolder(string $folder): array {
1054
        return $this->requestAndResponse('DELETE', [$this->escapeString($folder)], true);
1055
    }
1056
1057
    /**
1058
     * Subscribe to a folder
1059
     *
1060
     * @param string $folder folder name
1061
     * @return array success
1062
     *
1063
     * @throws ImapBadRequestException
1064
     * @throws ImapServerErrorException
1065
     * @throws RuntimeException
1066
     */
1067
    public function subscribeFolder(string $folder): array {
1068
        return $this->requestAndResponse('SUBSCRIBE', [$this->escapeString($folder)], true);
1069
    }
1070
1071
    /**
1072
     * Unsubscribe from a folder
1073
     *
1074
     * @param string $folder folder name
1075
     * @return array success
1076
     *
1077
     * @throws ImapBadRequestException
1078
     * @throws ImapServerErrorException
1079
     * @throws RuntimeException
1080
     */
1081
    public function unsubscribeFolder(string $folder): array {
1082
        return $this->requestAndResponse('UNSUBSCRIBE', [$this->escapeString($folder)], true);
1083
    }
1084
1085
    /**
1086
     * Apply session saved changes to the server
1087
     *
1088
     * @return array success
1089
     * @throws ImapBadRequestException
1090
     * @throws ImapServerErrorException
1091
     * @throws RuntimeException
1092
     */
1093
    public function expunge(): array {
1094
        return $this->requestAndResponse('EXPUNGE');
1095
    }
1096
1097
    /**
1098
     * Send noop command
1099
     *
1100
     * @return array success
1101
     * @throws ImapBadRequestException
1102
     * @throws ImapServerErrorException
1103
     * @throws RuntimeException
1104
     */
1105
    public function noop(): array {
1106
        return $this->requestAndResponse('NOOP');
1107
    }
1108
1109
    /**
1110
     * Retrieve the quota level settings, and usage statics per mailbox
1111
     *
1112
     * @param $username
1113
     * @return array
1114
     *
1115
     * @throws ImapBadRequestException
1116
     * @throws ImapServerErrorException
1117
     * @throws RuntimeException
1118
     */
1119
    public function getQuota($username): array {
1120
        $command = "GETQUOTA";
1121
        $params = ['"#user/' . $username . '"'];
1122
1123
        return $this->requestAndResponse($command, $params);
1124
    }
1125
1126
    /**
1127
     * Retrieve the quota settings per user
1128
     *
1129
     * @param string $quota_root
1130
     * @return array
1131
     *
1132
     * @throws ImapBadRequestException
1133
     * @throws ImapServerErrorException
1134
     * @throws RuntimeException
1135
     */
1136
    public function getQuotaRoot(string $quota_root = 'INBOX'): array {
1137
        $command = "QUOTA";
1138
        $params = [$quota_root];
1139
1140
        return $this->requestAndResponse($command, $params);
1141
    }
1142
1143
    /**
1144
     * Send idle command
1145
     *
1146
     * @throws RuntimeException
1147
     */
1148
    public function idle() {
1149
        $this->sendRequest("IDLE");
1150
        if (!$this->assumedNextLine('+ ')) {
1151
            throw new RuntimeException('idle failed');
1152
        }
1153
    }
1154
1155
    /**
1156
     * Send done command
1157
     * @throws RuntimeException
1158
     */
1159
    public function done(): bool {
1160
        $this->write("DONE");
1161
        if (!$this->assumedNextTaggedLine('OK', $tags)) {
1162
            throw new RuntimeException('done failed');
1163
        }
1164
        return true;
1165
    }
1166
1167
    /**
1168
     * Search for matching messages
1169
     *
1170
     * @param array $params
1171
     * @param int $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use
1172
     *                 message numbers instead.
1173
     *
1174
     * @return array message ids
1175
     * @throws ImapBadRequestException
1176
     * @throws ImapServerErrorException
1177
     * @throws RuntimeException
1178
     */
1179
    public function search(array $params, $uid = IMAP::ST_UID): array {
1180
        $command = $this->buildUIDCommand("SEARCH", $uid);
1181
        $response = $this->requestAndResponse($command, $params);
1182
1183
        foreach ($response as $ids) {
1184
            if ($ids[0] === 'SEARCH') {
1185
                array_shift($ids);
0 ignored issues
show
Bug introduced by
$ids of type true is incompatible with the type array expected by parameter $array of array_shift(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1185
                array_shift(/** @scrutinizer ignore-type */ $ids);
Loading history...
1186
1187
                return $ids;
1188
            }
1189
        }
1190
1191
        return [];
1192
    }
1193
1194
    /**
1195
     * Get a message overview
1196
     * @param string $sequence
1197
     * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use
1198
     * message numbers instead.
1199
     *
1200
     * @return array
1201
     * @throws RuntimeException
1202
     * @throws MessageNotFoundException
1203
     * @throws InvalidMessageDateException
1204
     */
1205
    public function overview(string $sequence, $uid = IMAP::ST_UID): array {
1206
        $result = [];
1207
        list($from, $to) = explode(":", $sequence);
1208
1209
        $uids = $this->getUid();
1210
        $ids = [];
1211
        foreach ($uids as $msgn => $v) {
1212
            $id = $uid ? $v : $msgn;
1213
            if ( ($to >= $id && $from <= $id) || ($to === "*" && $from <= $id) ){
1214
                $ids[] = $id;
1215
            }
1216
        }
1217
        $headers = $this->headers($ids, "RFC822", $uid);
1218
        foreach ($headers as $id => $raw_header) {
1219
            $result[$id] = (new Header($raw_header, false))->getAttributes();
1220
        }
1221
        return $result;
1222
    }
1223
1224
    /**
1225
     * Enable the debug mode
1226
     */
1227
    public function enableDebug(){
1228
        $this->debug = true;
1229
    }
1230
1231
    /**
1232
     * Disable the debug mode
1233
     */
1234
    public function disableDebug(){
1235
        $this->debug = false;
1236
    }
1237
1238
    /**
1239
     * Build a valid UID number set
1240
     * @param $from
1241
     * @param null $to
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $to is correct as it would always require null to be passed?
Loading history...
1242
     *
1243
     * @return int|string
1244
     */
1245
    public function buildSet($from, $to = null) {
1246
        $set = (int)$from;
1247
        if ($to !== null) {
0 ignored issues
show
introduced by
The condition $to !== null is always false.
Loading history...
1248
            $set .= ':' . ($to == INF ? '*' : (int)$to);
1249
        }
1250
        return $set;
1251
    }
1252
}
1253