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
|
|||||
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
$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
Loading history...
|
|||||
204 | $token = substr($token, 0, $chars); |
||||
0 ignored issues
–
show
$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
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
|
|||||
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
|
|||||
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
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
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
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
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
Loading history...
|
|||||
508 | |||||
509 | $capabilities = []; |
||||
510 | foreach ($response as $line) { |
||||
511 | $capabilities = array_merge($capabilities, $line); |
||||
0 ignored issues
–
show
$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
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
|
|||||
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
|
|||||
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
|
|||||
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
|
|||||
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
|
|||||
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
|
|||||
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
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
Loading history...
|
|||||
752 | try { |
||||
753 | $this->setUidCache($this->fetch('UID', 1, INF)); // set cache for this folder |
||||
0 ignored issues
–
show
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
Loading history...
|
|||||
754 | } catch (RuntimeException $e) {} |
||||
0 ignored issues
–
show
Coding Style
Comprehensibility
introduced
by
|
|||||
755 | } |
||||
756 | $uids = $this->uid_cache; |
||||
757 | |||||
758 | if ($id == null) { |
||||
0 ignored issues
–
show
|
|||||
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
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
Loading history...
|
|||||
810 | |||||
811 | if ($list[0] === true) { |
||||
0 ignored issues
–
show
|
|||||
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
|
|||||
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
|
|||||
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
|
|||||
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
|
|||||
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
|
|||||
875 | * @param null $date date for new message |
||||
0 ignored issues
–
show
|
|||||
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
|
|||||
887 | $tokens[] = $this->escapeList($flags); |
||||
888 | } |
||||
889 | if ($date !== null) { |
||||
0 ignored issues
–
show
|
|||||
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
|
|||||
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
|
|||||
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
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
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
$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
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
|
|||||
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
|
|||||
1248 | $set .= ':' . ($to == INF ? '*' : (int)$to); |
||||
1249 | } |
||||
1250 | return $set; |
||||
1251 | } |
||||
1252 | } |
||||
1253 |
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.