Passed
Push — master ( 0897d6...647d71 )
by Malte
03:33
created

ImapProtocol::disableDebug()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 2
rs 10
cc 1
nc 1
nop 0
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\InvalidMessageDateException;
19
use Webklex\PHPIMAP\Exceptions\MessageNotFoundException;
20
use Webklex\PHPIMAP\Exceptions\RuntimeException;
21
use Webklex\PHPIMAP\Header;
22
use Webklex\PHPIMAP\IMAP;
23
24
/**
25
 * Class ImapProtocol
26
 *
27
 * @package Webklex\PHPIMAP\Connection\Protocols
28
 */
29
class ImapProtocol extends Protocol {
30
31
    /**
32
     * Request noun
33
     * @var int
34
     */
35
    protected $noun = 0;
36
37
    /**
38
     * Imap constructor.
39
     * @param bool $cert_validation set to false to skip SSL certificate validation
40
     * @param mixed $encryption Connection encryption method
41
     */
42
    public function __construct(bool $cert_validation = true, $encryption = false) {
43
        $this->setCertValidation($cert_validation);
44
        $this->encryption = $encryption;
45
    }
46
47
    /**
48
     * Public destructor
49
     */
50
    public function __destruct() {
51
        $this->logout();
52
    }
53
54
    /**
55
     * Open connection to IMAP server
56
     * @param string $host hostname or IP address of IMAP server
57
     * @param int|null $port of IMAP server, default is 143 and 993 for ssl
58
     *
59
     * @throws ConnectionFailedException
60
     */
61
    public function connect(string $host, $port = null) {
62
        $transport = 'tcp';
63
        $encryption = '';
64
65
        if ($this->encryption) {
66
            $encryption = strtolower($this->encryption);
67
            if (in_array($encryption, ['ssl', 'tls'])) {
68
                $transport = $encryption;
69
                $port = $port === null ? 993 : $port;
70
            }
71
        }
72
        $port = $port === null ? 143 : $port;
73
        try {
74
            $this->stream = $this->createStream($transport, $host, $port, $this->connection_timeout);
75
            if (!$this->assumedNextLine('* OK')) {
76
                throw new ConnectionFailedException('connection refused');
77
            }
78
            if ($encryption == 'starttls') {
79
                $this->enableStartTls();
80
            }
81
        } catch (Exception $e) {
82
            throw new ConnectionFailedException('connection failed', 0, $e);
83
        }
84
    }
85
86
    /**
87
     * Enable tls on the current connection
88
     *
89
     * @throws ConnectionFailedException
90
     * @throws RuntimeException
91
     */
92
    protected function enableStartTls(){
93
        $response = $this->requestAndResponse('STARTTLS');
94
        $result = $response && stream_socket_enable_crypto($this->stream, true, $this->getCryptoMethod());
0 ignored issues
show
Bug introduced by
It seems like $this->stream can also be of type boolean; however, parameter $stream of stream_socket_enable_crypto() does only seem to accept resource, 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

94
        $result = $response && stream_socket_enable_crypto(/** @scrutinizer ignore-type */ $this->stream, true, $this->getCryptoMethod());
Loading history...
95
        if (!$result) {
96
            throw new ConnectionFailedException('failed to enable TLS');
97
        }
98
    }
99
100
    /**
101
     * Get the next line from stream
102
     *
103
     * @return string next line
104
     * @throws RuntimeException
105
     */
106
    public function nextLine(): string {
107
        $line = "";
108
        while (($next_char = fread($this->stream, 1)) !== false && $next_char !== "\n") {
0 ignored issues
show
Bug introduced by
It seems like $this->stream can also be of type boolean; however, parameter $stream of fread() does only seem to accept resource, 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

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

197
                        $line = substr($token, /** @scrutinizer ignore-type */ $chars);
Loading history...
198
                        $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

198
                        $token = substr($token, 0, /** @scrutinizer ignore-type */ $chars);
Loading history...
199
                    } else {
200
                        $line .= $this->nextLine();
201
                    }
202
                    $tokens[] = $token;
203
                    $line = trim($line) . ' ';
204
                    continue;
205
                }
206
            }
207
            if ($stack && $token[strlen($token) - 1] == ')') {
208
                // closing braces are not separated by spaces, so we need to count them
209
                $braces = strlen($token);
210
                $token = rtrim($token, ')');
211
                // only count braces if more than one
212
                $braces -= strlen($token) + 1;
213
                // only add if token had more than just closing braces
214
                if (rtrim($token) != '') {
215
                    $tokens[] = rtrim($token);
216
                }
217
                $token = $tokens;
218
                $tokens = array_pop($stack);
219
                // special handline if more than one closing brace
220
                while ($braces-- > 0) {
221
                    $tokens[] = $token;
222
                    $token = $tokens;
223
                    $tokens = array_pop($stack);
224
                }
225
            }
226
            $tokens[] = $token;
227
            $line = substr($line, $pos + 1);
228
        }
229
230
        // maybe the server forgot to send some closing braces
231
        while ($stack) {
232
            $child = $tokens;
233
            $tokens = array_pop($stack);
234
            $tokens[] = $child;
235
        }
236
237
        return $tokens;
238
    }
239
240
    /**
241
     * Read abd decode a response "line"
242
     * @param array|string $tokens to decode
243
     * @param string $wantedTag targeted tag
244
     * @param bool $dontParse if true only the unparsed line is returned in $tokens
245
     *
246
     * @return bool
247
     * @throws RuntimeException
248
     */
249
    public function readLine(&$tokens = [], string $wantedTag = '*', bool $dontParse = false): bool {
250
        $line = $this->nextTaggedLine($tag); // get next tag
251
        if (!$dontParse) {
252
            $tokens = $this->decodeLine($line);
253
        } else {
254
            $tokens = $line;
255
        }
256
257
        // if tag is wanted tag we might be at the end of a multiline response
258
        return $tag == $wantedTag;
259
    }
260
261
    /**
262
     * Read all lines of response until given tag is found
263
     * @param string $tag request tag
264
     * @param bool $dontParse if true every line is returned unparsed instead of the decoded tokens
265
     *
266
     * @return array|bool|null tokens if success, false if error, null if bad request
267
     * @throws RuntimeException
268
     */
269
    public function readResponse(string $tag, bool $dontParse = false) {
270
        $lines = [];
271
        $tokens = null; // define $tokens variable before first use
272
        do {
273
            $readAll = $this->readLine($tokens, $tag, $dontParse);
274
            $lines[] = $tokens;
275
        } while (!$readAll);
276
277
        if ($dontParse) {
278
            // First two chars are still needed for the response code
279
            $tokens = [substr($tokens, 0, 2)];
280
        }
281
282
        // last line has response code
283
        if ($tokens[0] == 'OK') {
284
            return $lines ? $lines : true;
0 ignored issues
show
introduced by
$lines is an empty array, thus is always false.
Loading history...
285
        } elseif ($tokens[0] == 'NO') {
286
            return false;
287
        }
288
289
        return null;
290
    }
291
292
    /**
293
     * Send a new request
294
     * @param string $command
295
     * @param array $tokens additional parameters to command, use escapeString() to prepare
296
     * @param string|null $tag provide a tag otherwise an autogenerated is returned
297
     *
298
     * @throws RuntimeException
299
     */
300
    public function sendRequest(string $command, array $tokens = [], string &$tag = null) {
301
        if (!$tag) {
302
            $this->noun++;
303
            $tag = 'TAG' . $this->noun;
304
        }
305
306
        $line = $tag . ' ' . $command;
307
308
        foreach ($tokens as $token) {
309
            if (is_array($token)) {
310
                $this->write($line . ' ' . $token[0]);
311
                if (!$this->assumedNextLine('+ ')) {
312
                    throw new RuntimeException('failed to send literal string');
313
                }
314
                $line = $token[1];
315
            } else {
316
                $line .= ' ' . $token;
317
            }
318
        }
319
        $this->write($line);
320
    }
321
322
    /**
323
     * Write data to the current stream
324
     * @param string $data
325
     * @return void
326
     * @throws RuntimeException
327
     */
328
    public function write(string $data) {
329
        if ($this->debug) echo ">> ".$data ."\n";
330
331
        if (fwrite($this->stream, $data . "\r\n") === false) {
0 ignored issues
show
Bug introduced by
It seems like $this->stream can also be of type boolean; however, parameter $stream of fwrite() does only seem to accept resource, 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

331
        if (fwrite(/** @scrutinizer ignore-type */ $this->stream, $data . "\r\n") === false) {
Loading history...
332
            throw new RuntimeException('failed to write - connection closed?');
333
        }
334
    }
335
336
    /**
337
     * Send a request and get response at once
338
     * @param string $command
339
     * @param array $tokens parameters as in sendRequest()
340
     * @param bool $dontParse if true unparsed lines are returned instead of tokens
341
     *
342
     * @return array|bool|null response as in readResponse()
343
     * @throws RuntimeException
344
     */
345
    public function requestAndResponse(string $command, array $tokens = [], bool $dontParse = false) {
346
        $this->sendRequest($command, $tokens, $tag);
347
348
        return $this->readResponse($tag, $dontParse);
349
    }
350
351
    /**
352
     * Escape one or more literals i.e. for sendRequest
353
     * @param string|array $string the literal/-s
354
     *
355
     * @return string|array escape literals, literals with newline ar returned
356
     *                      as array('{size}', 'string');
357
     */
358
    public function escapeString($string) {
359
        if (func_num_args() < 2) {
360
            if (strpos($string, "\n") !== false) {
361
                return ['{' . strlen($string) . '}', $string];
362
            } else {
363
                return '"' . str_replace(['\\', '"'], ['\\\\', '\\"'], $string) . '"';
364
            }
365
        }
366
        $result = [];
367
        foreach (func_get_args() as $string) {
0 ignored issues
show
introduced by
$string is overwriting one of the parameters of this function.
Loading history...
368
            $result[] = $this->escapeString($string);
369
        }
370
        return $result;
371
    }
372
373
    /**
374
     * Escape a list with literals or lists
375
     * @param array $list list with literals or lists as PHP array
376
     *
377
     * @return string escaped list for imap
378
     */
379
    public function escapeList(array $list): string {
380
        $result = [];
381
        foreach ($list as $v) {
382
            if (!is_array($v)) {
383
                $result[] = $v;
384
                continue;
385
            }
386
            $result[] = $this->escapeList($v);
387
        }
388
        return '(' . implode(' ', $result) . ')';
389
    }
390
391
    /**
392
     * Login to a new session.
393
     * @param string $user username
394
     * @param string $password password
395
     *
396
     * @return bool|mixed
397
     * @throws AuthFailedException
398
     */
399
    public function login(string $user, string $password): bool {
400
        try {
401
            $response = $this->requestAndResponse('LOGIN', $this->escapeString($user, $password), true);
0 ignored issues
show
Bug introduced by
It seems like $this->escapeString($user, $password) 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

401
            $response = $this->requestAndResponse('LOGIN', /** @scrutinizer ignore-type */ $this->escapeString($user, $password), true);
Loading history...
402
            return $response !== null && $response !== false;
403
        } catch (RuntimeException $e) {
404
            throw new AuthFailedException("failed to authenticate", 0, $e);
405
        }
406
    }
407
408
    /**
409
     * Authenticate your current IMAP session.
410
     * @param string $user username
411
     * @param string $token access token
412
     *
413
     * @return bool
414
     * @throws AuthFailedException
415
     */
416
    public function authenticate(string $user, string $token): bool {
417
        try {
418
            $authenticateParams = ['XOAUTH2', base64_encode("user=$user\1auth=Bearer $token\1\1")];
419
            $this->sendRequest('AUTHENTICATE', $authenticateParams);
420
421
            while (true) {
422
                $response = "";
423
                $is_plus = $this->readLine($response, '+', true);
424
                if ($is_plus) {
425
                    // try to log the challenge somewhere where it can be found
426
                    error_log("got an extra server challenge: $response");
427
                    // respond with an empty response.
428
                    $this->sendRequest('');
429
                } else {
430
                    if (preg_match('/^NO /i', $response) ||
431
                        preg_match('/^BAD /i', $response)) {
432
                        error_log("got failure response: $response");
433
                        return false;
434
                    } else if (preg_match("/^OK /i", $response)) {
435
                        return true;
436
                    }
437
                }
438
            }
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...
439
        } catch (RuntimeException $e) {
440
            throw new AuthFailedException("failed to authenticate", 0, $e);
441
        }
442
    }
443
444
    /**
445
     * Logout of imap server
446
     *
447
     * @return bool success
448
     */
449
    public function logout(): bool {
450
        $result = false;
451
        if ($this->stream) {
452
            try {
453
                $result = $this->requestAndResponse('LOGOUT', [], true);
454
            } catch (Exception $e) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
455
            fclose($this->stream);
0 ignored issues
show
Bug introduced by
It seems like $this->stream can also be of type true; however, parameter $stream of fclose() does only seem to accept resource, 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

455
            fclose(/** @scrutinizer ignore-type */ $this->stream);
Loading history...
456
            $this->stream = null;
457
            $this->uid_cache = null;
458
        }
459
460
        return $result !== false;
461
    }
462
463
    /**
464
     * Check if the current session is connected
465
     *
466
     * @return bool
467
     */
468
    public function connected(): bool {
469
        return (boolean) $this->stream;
470
    }
471
472
    /**
473
     * Get an array of available capabilities
474
     *
475
     * @return array list of capabilities
476
     * @throws RuntimeException
477
     */
478
    public function getCapabilities(): array {
479
        $response = $this->requestAndResponse('CAPABILITY');
480
481
        if (!$response) return [];
0 ignored issues
show
Bug Best Practice introduced by
The expression $response of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
482
483
        $capabilities = [];
484
        foreach ($response as $line) {
0 ignored issues
show
Bug introduced by
The expression $response of type true is not traversable.
Loading history...
485
            $capabilities = array_merge($capabilities, $line);
486
        }
487
        return $capabilities;
488
    }
489
490
    /**
491
     * Examine and select have the same response.
492
     * @param string $command can be 'EXAMINE' or 'SELECT'
493
     * @param string $folder target folder
494
     *
495
     * @return bool|array
496
     * @throws RuntimeException
497
     */
498
    public function examineOrSelect(string $command = 'EXAMINE', string $folder = 'INBOX') {
499
        $this->sendRequest($command, [$this->escapeString($folder)], $tag);
500
501
        $result = [];
502
        $tokens = null; // define $tokens variable before first use
503
        while (!$this->readLine($tokens, $tag)) {
504
            if ($tokens[0] == 'FLAGS') {
505
                array_shift($tokens);
506
                $result['flags'] = $tokens;
507
                continue;
508
            }
509
            switch ($tokens[1]) {
510
                case 'EXISTS':
511
                case 'RECENT':
512
                    $result[strtolower($tokens[1])] = $tokens[0];
513
                    break;
514
                case '[UIDVALIDITY':
515
                    $result['uidvalidity'] = (int)$tokens[2];
516
                    break;
517
                case '[UIDNEXT':
518
                    $result['uidnext'] = (int)$tokens[2];
519
                    break;
520
                default:
521
                    // ignore
522
                    break;
523
            }
524
        }
525
526
        if ($tokens[0] != 'OK') {
527
            return false;
528
        }
529
        return $result;
530
    }
531
532
    /**
533
     * Change the current folder
534
     * @param string $folder change to this folder
535
     *
536
     * @return bool|array see examineOrselect()
537
     * @throws RuntimeException
538
     */
539
    public function selectFolder(string $folder = 'INBOX') {
540
        $this->uid_cache = null;
541
542
        return $this->examineOrSelect('SELECT', $folder);
543
    }
544
545
    /**
546
     * Examine a given folder
547
     * @param string $folder examine this folder
548
     *
549
     * @return bool|array see examineOrselect()
550
     * @throws RuntimeException
551
     */
552
    public function examineFolder(string $folder = 'INBOX') {
553
        return $this->examineOrSelect('EXAMINE', $folder);
554
    }
555
556
    /**
557
     * Fetch one or more items of one or more messages
558
     * @param string|array $items items to fetch [RFC822.HEADER, FLAGS, RFC822.TEXT, etc]
559
     * @param int|array $from message for items or start message if $to !== null
560
     * @param int|null $to if null only one message ($from) is fetched, else it's the
561
     *                             last message, INF means last message available
562
     * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use
563
     * message numbers instead.
564
     *
565
     * @return string|array if only one item of one message is fetched it's returned as string
566
     *                      if items of one message are fetched it's returned as (name => value)
567
     *                      if one items of messages are fetched it's returned as (msgno => value)
568
     *                      if items of messages are fetched it's returned as (msgno => (name => value))
569
     * @throws RuntimeException
570
     */
571
    public function fetch($items, $from, $to = null, $uid = IMAP::ST_UID) {
572
        if (is_array($from)) {
573
            $set = implode(',', $from);
574
        } elseif ($to === null) {
575
            $set = (int)$from;
576
        } elseif ($to === INF) {
0 ignored issues
show
introduced by
The condition $to === Webklex\PHPIMAP\Connection\Protocols\INF is always false.
Loading history...
577
            $set = (int)$from . ':*';
578
        } else {
579
            $set = (int)$from . ':' . (int)$to;
580
        }
581
582
        $items = (array)$items;
583
        $itemList = $this->escapeList($items);
584
585
        $this->sendRequest($this->buildUIDCommand("FETCH", $uid), [$set, $itemList], $tag);
586
        $result = [];
587
        $tokens = null; // define $tokens variable before first use
588
        while (!$this->readLine($tokens, $tag)) {
589
            // ignore other responses
590
            if ($tokens[1] != 'FETCH') {
591
                continue;
592
            }
593
594
            // find array key of UID value; try the last elements, or search for it
595
            if ($uid) {
596
                $count = count($tokens[2]);
597
                if ($tokens[2][$count - 2] == 'UID') {
598
                    $uidKey = $count - 1;
599
                } else if ($tokens[2][0] == 'UID') {
600
                    $uidKey = 1;
601
                } else {
602
                    $uidKey = array_search('UID', $tokens[2]) + 1;
603
                }
604
            }
605
606
            // ignore other messages
607
            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...
608
                continue;
609
            }
610
            $data = "";
611
612
            // if we only want one item we return that one directly
613
            if (count($items) == 1) {
614
                if ($tokens[2][0] == $items[0]) {
615
                    $data = $tokens[2][1];
616
                } elseif ($uid && $tokens[2][2] == $items[0]) {
617
                    $data = $tokens[2][3];
618
                } else {
619
                    // maybe the server send an other field we didn't wanted
620
                    $count = count($tokens[2]);
621
                    // we start with 2, because 0 was already checked
622
                    for ($i = 2; $i < $count; $i += 2) {
623
                        if ($tokens[2][$i] != $items[0]) {
624
                            continue;
625
                        }
626
                        $data = $tokens[2][$i + 1];
627
                        break;
628
                    }
629
                }
630
            } else {
631
                $data = [];
632
                while (key($tokens[2]) !== null) {
633
                    $data[current($tokens[2])] = next($tokens[2]);
634
                    next($tokens[2]);
635
                }
636
            }
637
638
            // if we want only one message we can ignore everything else and just return
639
            if ($to === null && !is_array($from) && ($uid ? $tokens[2][$uidKey] == $from : $tokens[0] == $from)) {
640
                // we still need to read all lines
641
                while (!$this->readLine($tokens, $tag))
642
643
                    return $data;
644
            }
645
            if ($uid) {
646
                $result[$tokens[2][$uidKey]] = $data;
647
            }else{
648
                $result[$tokens[0]] = $data;
649
            }
650
        }
651
652
        if ($to === null && !is_array($from)) {
653
            throw new RuntimeException('the single id was not found in response');
654
        }
655
656
        return $result;
657
    }
658
659
    /**
660
     * Fetch message headers
661
     * @param array|int $uids
662
     * @param string $rfc
663
     * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use
664
     * message numbers instead.
665
     *
666
     * @return array
667
     * @throws RuntimeException
668
     */
669
    public function content($uids, string $rfc = "RFC822", $uid = IMAP::ST_UID): array {
670
        return $this->fetch(["$rfc.TEXT"], $uids, null, $uid);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->fetch(arra...T'), $uids, null, $uid) could return the type string which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
671
    }
672
673
    /**
674
     * Fetch message headers
675
     * @param array|int $uids
676
     * @param string $rfc
677
     * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use
678
     * message numbers instead.
679
     *
680
     * @return array
681
     * @throws RuntimeException
682
     */
683
    public function headers($uids, string $rfc = "RFC822", $uid = IMAP::ST_UID): array{
684
        return $this->fetch(["$rfc.HEADER"], $uids, null, $uid);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->fetch(arra...R'), $uids, null, $uid) could return the type string which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
685
    }
686
687
    /**
688
     * Fetch message flags
689
     * @param array|int $uids
690
     * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use
691
     * message numbers instead.
692
     *
693
     * @return array
694
     * @throws RuntimeException
695
     */
696
    public function flags($uids, $uid = IMAP::ST_UID): array {
697
        return $this->fetch(["FLAGS"], $uids, null, $uid);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->fetch(arra...S'), $uids, null, $uid) could return the type string which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
698
    }
699
700
    /**
701
     * Get uid for a given id
702
     * @param int|null $id message number
703
     *
704
     * @return array|string message number for given message or all messages as array
705
     * @throws MessageNotFoundException
706
     */
707
    public function getUid($id = null) {
708
        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...
709
            try {
710
                $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

710
                $this->setUidCache($this->fetch('UID', 1, /** @scrutinizer ignore-type */ INF)); // set cache for this folder
Loading history...
Bug introduced by
It seems like $this->fetch('UID', 1, W...nnection\Protocols\INF) can also be of type string; however, parameter $uids of Webklex\PHPIMAP\Connecti...Protocol::setUidCache() does only seem to accept array|null, 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

710
                $this->setUidCache(/** @scrutinizer ignore-type */ $this->fetch('UID', 1, INF)); // set cache for this folder
Loading history...
711
            } catch (RuntimeException $e) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
712
        }
713
        $uids = $this->uid_cache;
714
715
        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...
716
            return $uids;
717
        }
718
719
        foreach ($uids as $k => $v) {
720
            if ($k == $id) {
721
                return $v;
722
            }
723
        }
724
725
        // clear uid cache and run method again
726
        if ($this->enable_uid_cache && $this->uid_cache) {
727
            $this->setUidCache(null);
728
            return $this->getUid($id);
729
        }
730
731
        throw new MessageNotFoundException('unique id not found');
732
    }
733
734
    /**
735
     * Get a message number for a uid
736
     * @param string $id uid
737
     *
738
     * @return int message number
739
     * @throws MessageNotFoundException
740
     */
741
    public function getMessageNumber(string $id): int {
742
        $ids = $this->getUid();
743
        foreach ($ids as $k => $v) {
744
            if ($v == $id) {
745
                return $k;
746
            }
747
        }
748
749
        throw new MessageNotFoundException('message number not found');
750
    }
751
752
    /**
753
     * Get a list of available folders
754
     * @param string $reference mailbox reference for list
755
     * @param string $folder mailbox name match with wildcards
756
     *
757
     * @return array folders that matched $folder as array(name => array('delimiter' => .., 'flags' => ..))
758
     * @throws RuntimeException
759
     */
760
    public function folders(string $reference = '', string $folder = '*'): array {
761
        $result = [];
762
        $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

762
        $list = $this->requestAndResponse('LIST', /** @scrutinizer ignore-type */ $this->escapeString($reference, $folder));
Loading history...
763
        if (!$list || $list === true) {
0 ignored issues
show
introduced by
The condition $list === true is always true.
Loading history...
Bug Best Practice introduced by
The expression $list of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
764
            return $result;
765
        }
766
767
        foreach ($list as $item) {
768
            if (count($item) != 4 || $item[0] != 'LIST') {
769
                continue;
770
            }
771
            $result[$item[3]] = ['delimiter' => $item[2], 'flags' => $item[1]];
772
        }
773
774
        return $result;
775
    }
776
777
    /**
778
     * Manage flags
779
     * @param array $flags flags to set, add or remove - see $mode
780
     * @param int $from message for items or start message if $to !== null
781
     * @param int|null $to if null only one message ($from) is fetched, else it's the
782
     *                             last message, INF means last message available
783
     * @param string|null $mode '+' to add flags, '-' to remove flags, everything else sets the flags as given
784
     * @param bool $silent if false the return values are the new flags for the wanted messages
785
     * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use
786
     * message numbers instead.
787
     * @param null|string $item command used to store a flag
788
     *
789
     * @return bool|array new flags if $silent is false, else true or false depending on success
790
     * @throws RuntimeException
791
     */
792
    public function store(array $flags, int $from, $to = null, $mode = null, bool $silent = true, $uid = IMAP::ST_UID, $item = null) {
793
        $flags = $this->escapeList($flags);
794
        $set = $this->buildSet($from, $to);
0 ignored issues
show
Bug introduced by
It seems like $to can also be of type integer; however, parameter $to of Webklex\PHPIMAP\Connecti...mapProtocol::buildSet() does only seem to accept null, 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

794
        $set = $this->buildSet($from, /** @scrutinizer ignore-type */ $to);
Loading history...
795
796
        $command = $this->buildUIDCommand("STORE", $uid);
797
        $item = ($mode == '-' ? "-" : "+").($item === null ? "FLAGS" : $item).($silent ? '.SILENT' : "");
798
799
        $response = $this->requestAndResponse($command, [$set, $item, $flags], $silent);
800
801
        if ($silent) {
802
            return (bool)$response;
803
        }
804
805
        $result = [];
806
        foreach ($response as $token) {
0 ignored issues
show
Bug introduced by
The expression $response of type boolean|null is not traversable.
Loading history...
807
            if ($token[1] != 'FETCH' || $token[2][0] != 'FLAGS') {
808
                continue;
809
            }
810
            $result[$token[0]] = $token[2][1];
811
        }
812
813
        return $result;
814
    }
815
816
    /**
817
     * Append a new message to given folder
818
     * @param string $folder name of target folder
819
     * @param string $message full message content
820
     * @param array|null $flags flags for new message
821
     * @param string $date date for new message
822
     *
823
     * @return bool success
824
     * @throws RuntimeException
825
     */
826
    public function appendMessage(string $folder, string $message, $flags = null, $date = null): bool {
827
        $tokens = [];
828
        $tokens[] = $this->escapeString($folder);
829
        if ($flags !== null) {
830
            $tokens[] = $this->escapeList($flags);
831
        }
832
        if ($date !== null) {
833
            $tokens[] = $this->escapeString($date);
834
        }
835
        $tokens[] = $this->escapeString($message);
836
837
        return $this->requestAndResponse('APPEND', $tokens, true);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->requestAnd...APPEND', $tokens, true) could return the type null which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
838
    }
839
840
    /**
841
     * Copy a message set from current folder to an other folder
842
     * @param string $folder destination folder
843
     * @param $from
844
     * @param int|null $to if null only one message ($from) is fetched, else it's the
845
     *                         last message, INF means last message available
846
     * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use
847
     * message numbers instead.
848
     *
849
     * @return bool success
850
     * @throws RuntimeException
851
     */
852
    public function copyMessage(string $folder, $from, $to = null, $uid = IMAP::ST_UID): bool {
853
        $set = $this->buildSet($from, $to);
0 ignored issues
show
Bug introduced by
It seems like $to can also be of type integer; however, parameter $to of Webklex\PHPIMAP\Connecti...mapProtocol::buildSet() does only seem to accept null, 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

853
        $set = $this->buildSet($from, /** @scrutinizer ignore-type */ $to);
Loading history...
854
        $command = $this->buildUIDCommand("COPY", $uid);
855
        return $this->requestAndResponse($command, [$set, $this->escapeString($folder)], true);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->requestAnd...String($folder)), true) could return the type null which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
856
    }
857
858
    /**
859
     * Copy multiple messages to the target folder
860
     *
861
     * @param array $messages List of message identifiers
862
     * @param string $folder Destination folder
863
     * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use
864
     * message numbers instead.
865
     * @return array|bool Tokens if operation successful, false if an error occurred
866
     *
867
     * @throws RuntimeException
868
     */
869
    public function copyManyMessages(array $messages, string $folder, $uid = IMAP::ST_UID) {
870
        $command = $this->buildUIDCommand("COPY", $uid);
871
872
        $set = implode(',', $messages);
873
        $tokens = [$set, $this->escapeString($folder)];
874
875
        return $this->requestAndResponse($command, $tokens, true);
876
    }
877
878
    /**
879
     * Move a message set from current folder to an other folder
880
     * @param string $folder destination folder
881
     * @param $from
882
     * @param int|null $to if null only one message ($from) is fetched, else it's the
883
     *                         last message, INF means last message available
884
     * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use
885
     * message numbers instead.
886
     *
887
     * @return bool success
888
     * @throws RuntimeException
889
     */
890
    public function moveMessage(string $folder, $from, $to = null, $uid = IMAP::ST_UID): bool {
891
        $set = $this->buildSet($from, $to);
0 ignored issues
show
Bug introduced by
It seems like $to can also be of type integer; however, parameter $to of Webklex\PHPIMAP\Connecti...mapProtocol::buildSet() does only seem to accept null, 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

891
        $set = $this->buildSet($from, /** @scrutinizer ignore-type */ $to);
Loading history...
892
        $command = $this->buildUIDCommand("MOVE", $uid);
893
894
        return $this->requestAndResponse($command, [$set, $this->escapeString($folder)], true);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->requestAnd...String($folder)), true) could return the type null which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
895
    }
896
897
    /**
898
     * Move multiple messages to the target folder
899
     * @param array $messages List of message identifiers
900
     * @param string $folder Destination folder
901
     * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use
902
     * message numbers instead.
903
     *
904
     * @return array|bool Tokens if operation successful, false if an error occurred
905
     * @throws RuntimeException
906
     */
907
    public function moveManyMessages(array $messages, string $folder, $uid = IMAP::ST_UID) {
908
        $command = $this->buildUIDCommand("MOVE", $uid);
909
910
        $set = implode(',', $messages);
911
        $tokens = [$set, $this->escapeString($folder)];
912
913
        return $this->requestAndResponse($command, $tokens, true);
914
    }
915
916
    /**
917
     * Exchange identification information
918
     * Ref.: https://datatracker.ietf.org/doc/html/rfc2971
919
     *
920
     * @param null $ids
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $ids is correct as it would always require null to be passed?
Loading history...
921
     * @return array|bool|void|null
922
     *
923
     * @throws RuntimeException
924
     */
925
    public function ID($ids = null) {
926
        $token = "NIL";
927
        if (is_array($ids) && !empty($ids)) {
0 ignored issues
show
introduced by
The condition is_array($ids) is always false.
Loading history...
928
            $token = "(";
929
            foreach ($ids as $id) {
930
                $token .= '"'.$id.'" ';
931
            }
932
            $token = rtrim($token).")";
933
        }
934
935
        return $this->requestAndResponse("ID", [$token], true);
936
    }
937
938
    /**
939
     * Create a new folder (and parent folders if needed)
940
     * @param string $folder folder name
941
     *
942
     * @return bool success
943
     * @throws RuntimeException
944
     */
945
    public function createFolder(string $folder): bool {
946
        return $this->requestAndResponse('CREATE', [$this->escapeString($folder)], true);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->requestAnd...String($folder)), true) could return the type null which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
947
    }
948
949
    /**
950
     * Rename an existing folder
951
     * @param string $old old name
952
     * @param string $new new name
953
     *
954
     * @return bool success
955
     * @throws RuntimeException
956
     */
957
    public function renameFolder(string $old, string $new): bool {
958
        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

958
        return $this->requestAndResponse('RENAME', /** @scrutinizer ignore-type */ $this->escapeString($old, $new), true);
Loading history...
Bug Best Practice introduced by
The expression return $this->requestAnd...ring($old, $new), true) could return the type null which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
959
    }
960
961
    /**
962
     * Delete a folder
963
     * @param string $folder folder name
964
     *
965
     * @return bool success
966
     * @throws RuntimeException
967
     */
968
    public function deleteFolder(string $folder): bool {
969
        return $this->requestAndResponse('DELETE', [$this->escapeString($folder)], true);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->requestAnd...String($folder)), true) could return the type null which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
970
    }
971
972
    /**
973
     * Subscribe to a folder
974
     * @param string $folder folder name
975
     *
976
     * @return bool success
977
     * @throws RuntimeException
978
     */
979
    public function subscribeFolder(string $folder): bool {
980
        return $this->requestAndResponse('SUBSCRIBE', [$this->escapeString($folder)], true);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->requestAnd...String($folder)), true) could return the type null which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
981
    }
982
983
    /**
984
     * Unsubscribe from a folder
985
     * @param string $folder folder name
986
     *
987
     * @return bool success
988
     * @throws RuntimeException
989
     */
990
    public function unsubscribeFolder(string $folder): bool {
991
        return $this->requestAndResponse('UNSUBSCRIBE', [$this->escapeString($folder)], true);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->requestAnd...String($folder)), true) could return the type null which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
992
    }
993
994
    /**
995
     * Apply session saved changes to the server
996
     *
997
     * @return bool success
998
     * @throws RuntimeException
999
     */
1000
    public function expunge(): bool {
1001
        return $this->requestAndResponse('EXPUNGE');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->requestAndResponse('EXPUNGE') could return the type null which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
1002
    }
1003
1004
    /**
1005
     * Send noop command
1006
     *
1007
     * @return bool success
1008
     * @throws RuntimeException
1009
     */
1010
    public function noop(): bool {
1011
        return $this->requestAndResponse('NOOP');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->requestAndResponse('NOOP') could return the type null which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
1012
    }
1013
1014
    /**
1015
     * Retrieve the quota level settings, and usage statics per mailbox
1016
     * @param $username
1017
     *
1018
     * @return array
1019
     * @throws RuntimeException
1020
     */
1021
    public function getQuota($username): array {
1022
        return $this->requestAndResponse("GETQUOTA", ['"#user/'.$username.'"']);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->requestAnd...r/' . $username . '"')) returns the type boolean|null which is incompatible with the type-hinted return array.
Loading history...
1023
    }
1024
1025
    /**
1026
     * Retrieve the quota settings per user
1027
     * @param string $quota_root
1028
     *
1029
     * @return array
1030
     * @throws RuntimeException
1031
     */
1032
    public function getQuotaRoot(string $quota_root = 'INBOX'): array {
1033
        return $this->requestAndResponse("QUOTA", [$quota_root]);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->requestAnd...A', array($quota_root)) returns the type boolean|null which is incompatible with the type-hinted return array.
Loading history...
1034
    }
1035
1036
    /**
1037
     * Send idle command
1038
     *
1039
     * @throws RuntimeException
1040
     */
1041
    public function idle() {
1042
        $this->sendRequest("IDLE");
1043
        if (!$this->assumedNextLine('+ ')) {
1044
            throw new RuntimeException('idle failed');
1045
        }
1046
    }
1047
1048
    /**
1049
     * Send done command
1050
     * @throws RuntimeException
1051
     */
1052
    public function done(): bool {
1053
        $this->write("DONE");
1054
        if (!$this->assumedNextTaggedLine('OK', $tags)) {
1055
            throw new RuntimeException('done failed');
1056
        }
1057
        return true;
1058
    }
1059
1060
    /**
1061
     * Search for matching messages
1062
     * @param array $params
1063
     * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use
1064
     * message numbers instead.
1065
     *
1066
     * @return array message ids
1067
     * @throws RuntimeException
1068
     */
1069
    public function search(array $params, $uid = IMAP::ST_UID): array {
1070
        $command = $this->buildUIDCommand("SEARCH", $uid);
1071
        $response = $this->requestAndResponse($command, $params);
1072
        if (!$response) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $response of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
1073
            return $response;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $response returns the type false|null which is incompatible with the type-hinted return array.
Loading history...
1074
        }
1075
1076
        foreach ($response as $ids) {
0 ignored issues
show
Bug introduced by
The expression $response of type true is not traversable.
Loading history...
1077
            if ($ids[0] == 'SEARCH') {
1078
                array_shift($ids);
1079
                return $ids;
1080
            }
1081
        }
1082
        return [];
1083
    }
1084
1085
    /**
1086
     * Get a message overview
1087
     * @param string $sequence
1088
     * @param int|string $uid set to IMAP::ST_UID or any string representing the UID - set to IMAP::ST_MSGN to use
1089
     * message numbers instead.
1090
     *
1091
     * @return array
1092
     * @throws RuntimeException
1093
     * @throws MessageNotFoundException
1094
     * @throws InvalidMessageDateException
1095
     */
1096
    public function overview(string $sequence, $uid = IMAP::ST_UID): array {
1097
        $result = [];
1098
        list($from, $to) = explode(":", $sequence);
1099
1100
        $uids = $this->getUid();
1101
        $ids = [];
1102
        foreach ($uids as $msgn => $v) {
1103
            $id = $uid ? $v : $msgn;
1104
            if ( ($to >= $id && $from <= $id) || ($to === "*" && $from <= $id) ){
1105
                $ids[] = $id;
1106
            }
1107
        }
1108
        $headers = $this->headers($ids, "RFC822", $uid);
1109
        foreach ($headers as $id => $raw_header) {
1110
            $result[$id] = (new Header($raw_header, false))->getAttributes();
1111
        }
1112
        return $result;
1113
    }
1114
1115
    /**
1116
     * Enable the debug mode
1117
     */
1118
    public function enableDebug(){
1119
        $this->debug = true;
1120
    }
1121
1122
    /**
1123
     * Disable the debug mode
1124
     */
1125
    public function disableDebug(){
1126
        $this->debug = false;
1127
    }
1128
1129
    /**
1130
     * Build a valid UID number set
1131
     * @param $from
1132
     * @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...
1133
     *
1134
     * @return int|string
1135
     */
1136
    public function buildSet($from, $to = null) {
1137
        $set = (int)$from;
1138
        if ($to !== null) {
0 ignored issues
show
introduced by
The condition $to !== null is always false.
Loading history...
1139
            $set .= ':' . ($to == INF ? '*' : (int)$to);
1140
        }
1141
        return $set;
1142
    }
1143
}
1144