Connection::removeMessage()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
nc 1
nop 2
1
<?php
2
3
namespace PHPDaemon\Clients\IMAP;
4
5
use PHPDaemon\Network\ClientConnection;
6
use PHPDaemon\Core\Daemon;
7
8
class Connection extends ClientConnection
9
{
10
    const STATE_CONNECTING  = 0;
11
    const STATE_CONNECTED   = 1;
12
    const STATE_CREDS_SENT  = 2;
13
    const STATE_AUTHORIZED  = 3;
14
15
    const FLAG_PASSED       = '\Passed';
16
    const FLAG_ANSWERED     = '\Answered';
17
    const FLAG_SEEN         = '\Seen';
18
    const FLAG_UNSEEN       = '\Unseen';
19
    const FLAG_DELETED      = '\Deleted';
20
    const FLAG_DRAFT        = '\Draft';
21
    const FLAG_FLAGGED      = '\Flagged';
22
23
    /**
24
     * IMAP flags to search criteria
25
     * @var array
26
     */
27
    protected $searchFlags = [
28
        '\Recent'   => 'RECENT',
29
        '\Answered' => 'ANSWERED',
30
        '\Seen'     => 'SEEN',
31
        '\Unseen'   => 'UNSEEN',
32
        '\Deleted'  => 'DELETED',
33
        '\Draft'    => 'DRAFT',
34
        '\Flagged'  => 'FLAGGED',
35
    ];
36
37
    const TAG_LOGIN         = 'a01';
38
    const TAG_LIST          = 'a02';
39
    const TAG_SELECT        = 'a03';
40
    const TAG_FETCH         = 'a04';
41
    const TAG_SEARCH        = 'a05';
42
    const TAG_COUNT         = 'a06';
43
    const TAG_SIZE          = 'a07';
44
    const TAG_GETRAWMESSAGE = 'a08';
45
    const TAG_GETRAWHEADER  = 'a09';
46
    const TAG_GETRAWCONTENT = 'a10';
47
    const TAG_GETUID        = 'a11';
48
    const TAG_CREATEFOLDER  = 'a12';
49
    const TAG_DELETEFOLDER  = 'a13';
50
    const TAG_RENAMEFOLDER  = 'a14';
51
    const TAG_STORE         = 'a15';
52
    const TAG_DELETEMESSAGE = 'a16';
53
    const TAG_EXPUNGE       = 'a17';
54
    const TAG_LOGOUT        = 'a18';
55
    const TAG_STARTTLS      = 'a19';
56
57
    protected $EOL = "\r\n";
58
    
59
    protected $state;
60
    protected $lines = [];
61
    protected $blob = '';
62
    protected $blobOctetsLeft = 0;
63
64
    public function onReady()
65
    {
66
        $this->state = self::STATE_CONNECTING;
67
    }
68
69
    /**
70
     * escape a single literal
71
     * @param  $string
72
     * @return string escaped list for imap
73
     */
74
    protected function escapeString($string)
75
    {
76
        return '"' . strtr($string, ['\\' => '\\\\', '"' => '\\"']) . '"';
77
    }
78
79
    /**
80
     * escape a list with literals or lists
81
     *
82
     * @param  array $list list with literals or lists as PHP array
83
     * @return string escaped list for imap
84
     */
85
    protected function escapeList($list)
86
    {
87
        $result = [];
88
        foreach ($list as $v) {
89
            if (!is_array($v)) {
90
                $result[] = $v;
91
                continue;
92
            }
93
            $result[] = $this->escapeList($v);
94
        }
95
        return '(' . implode(' ', $result) . ')';
96
    }
97
98
    /**
99
     * split a given line in tokens. a token is literal of any form or a list
100
     *
101
     * @param  string $line line to decode
102
     * @return array tokens, literals are returned as string, lists as array
103
     */
104
    protected function decodeLine($line)
105
    {
106
        $tokens = [];
107
        $stack = [];
108
        /*
109
            We start to decode the response here. The understood tokens are:
110
                literal
111
                "literal" or also "lit\\er\"al"
112
                (literals*)
113
            All tokens are returned in an array. Literals in braces (the last understood
114
            token in the list) are returned as an array of tokens. I.e. the following response:
115
                "foo" baz bar ("f\\\"oo" bar)
116
            would be returned as:
117
                array('foo', 'baz', 'bar', array('f\\\"oo', 'bar'));
118
        */
119
        //  replace any trailing <NL> including spaces with a single space
120
        $line = rtrim($line) . ' ';
121
        while (($pos = strpos($line, ' ')) !== false) {
122
            $token = substr($line, 0, $pos);
123
            if (!strlen($token)) {
124
                continue;
125
            }
126
            while ($token[0] === '(') {
127
                array_push($stack, $tokens);
128
                $tokens = [];
129
                $token = substr($token, 1);
130
            }
131
            if ($token[0] === '"') {
132
                if (preg_match('%^\(*"((.|\\\\|\\")*?)" *%', $line, $matches)) {
133
                    $tokens[] = $matches[1];
134
                    $line = substr($line, strlen($matches[0]));
135
                    continue;
136
                }
137
            }
138
            if ($stack && $token[strlen($token) - 1] === ')') {
0 ignored issues
show
Bug Best Practice introduced by
The expression $stack 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...
139
                // closing braces are not separated by spaces, so we need to count them
140
                $braces = strlen($token);
141
                $token = rtrim($token, ')');
142
                // only count braces if more than one
143
                $braces -= strlen($token) + 1;
144
                // only add if token had more than just closing braces
145
                if (rtrim($token) != '') {
146
                    $tokens[] = rtrim($token);
147
                }
148
                $token = $tokens;
149
                $tokens = array_pop($stack);
150
                // special handline if more than one closing brace
151
                while ($braces-- > 0) {
152
                    $tokens[] = $token;
153
                    $token = $tokens;
154
                    $tokens = array_pop($stack);
155
                }
156
            }
157
            $tokens[] = $token;
158
            $line = substr($line, $pos + 1);
159
        }
160
        // maybe the server forgot to send some closing braces
161
        while ($stack) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $stack 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...
162
            $child = $tokens;
163
            $tokens = array_pop($stack);
164
            $tokens[] = $child;
165
        }
166
        return $tokens;
167
    }
168
169
    /**
170
      * @param array $items
171
      * @param string $from
172
      * @param string $to
0 ignored issues
show
Documentation introduced by
Should the type for parameter $to not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
173
      * @param bool $uid
174
      * @param string $tag
175
     */
176
    protected function fetch($items, $from, $to = null, $uid = false, $tag = self::TAG_FETCH)
177
    {
178
        if (is_array($from)) {
179
            $set = implode(',', $from);
180
        } elseif ($to === null) {
181
            $set = (int) $from;
182
        } elseif ($to === INF) {
183
            $set = (int) $from . ':*';
184
        } else {
185
            $set = (int) $from . ':' . (int) $to;
186
        }
187
        $this->writeln($tag .($uid ? ' UID' : '')." FETCH $set ". $this->escapeList((array)$items) );
188
    }
189
190
    /**
191
     * @param array $flags
192
     * @param string $from
193
     * @param string $to
0 ignored issues
show
Documentation introduced by
Should the type for parameter $to not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
194
     * @param string $mode (+/-)
0 ignored issues
show
Documentation introduced by
Should the type for parameter $mode not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
195
     * @param bool $silent
196
     * @param string $tag
197
    */
198
    protected function store(array $flags, $from, $to = null, $mode = null, $silent = true, $tag = self::TAG_STORE)
199
    {
200
        $item = 'FLAGS';
201
        if ($mode == '+' || $mode == '-') {
202
            $item = $mode . $item;
203
        }
204
        if ($silent) {
205
            $item .= '.SILENT';
206
        }
207
        $flags = $this->escapeList($flags);
208
        $set = (int)$from;
209
        if ($to !== null) {
210
            $set .= ':' . ($to == INF ? '*' : (int)$to);
211
        }
212
        $this->writeln($tag . ' UID STORE ' . $set . ' ' . $item . ' ' . $flags );
213
    }
214
215
    /**
216
    * @param string $reference
217
    * @param string $mailbox
218
    */
219
    public function listFolders($cb, $reference = '', $mailbox = '*')
220
    {
221
        $this->onResponse->push($cb);
222
        $this->writeln(self::TAG_LIST .' LIST ' . $this->escapeString($reference)
223
            . ' ' .  $this->escapeString($mailbox) );
224
    }
225
226
    /**
227
    * @param string $box
228
    */
229
    public function selectBox($cb, $box = 'INBOX')
230
    {
231
        $this->onResponse->push($cb);
232
        $this->writeln(self::TAG_SELECT .' SELECT ' . $this->escapeString($box));
233
    }
234
235
    /**
236
    * @param string $tag
237
    */
238
    protected function expunge($tag = self::TAG_EXPUNGE)
239
    {
240
        $this->writeln($tag .' EXPUNGE');
241
    }
242
243
    /**
244
    * @param string $haystack
0 ignored issues
show
Bug introduced by
There is no parameter named $haystack. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
245
    * @param string $needle
0 ignored issues
show
Bug introduced by
There is no parameter named $needle. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
246
    */
247
    public function auth($cb, $login, $password)
248
    {
249
        $this->onResponse->push($cb);
250
        $this->writeln(self::TAG_LOGIN . " LOGIN $login $password");
251
    }
252
253
    /**
254
     * @param array $params
255
     * @param string $tag
256
     */
257
    protected function searchMessages($params, $tag = self::TAG_SEARCH)
258
    {
259
        $this->writeln("$tag UID SEARCH ".implode(' ', $params));
260
    }
261
262
    /**
263
     * @param string $haystack
264
     * @param string $needle
265
     */
266
    protected function startsWith($haystack, $needle)
267
    {
268
        // search backwards starting from haystack length characters from the end
269
        return $needle === '' || strrpos($haystack, $needle, -strlen($haystack)) !== false;
270
    }
271
272
    /**
273
     * @param array $lines
274
     */
275
    protected function decodeList($lines)
276
    {
277
        $list = [];
278
        foreach (array_map([$this, 'decodeLine'], $lines) as $tokens) {
279
            $folderEntry = [];
280
            if (!isset($tokens[0]) || $tokens[0] !== 'LIST') {
281
                continue;
282
            }
283
            if (isset($tokens[3])) {
284
                $folderEntry['name'] = $tokens[3];
285
            } else {
286
                continue;
287
            }
288
            if (isset($tokens[1])) {
289
                $folderEntry['flags'] = $tokens[1];
290
            } else {
291
                continue;
292
            }
293
            $list[] = $folderEntry;
294
        }
295
        return $list;
296
    }
297
298
    /**
299
     * @param array $lines
300
     */
301
    protected function decodeCount($lines)
302
    {
303
        foreach (array_map([$this, 'decodeLine'], $lines) as $tokens) {
304
            if (!isset($tokens[0]) || $tokens[0] !== 'SEARCH') {
305
                continue;
306
            }
307
            return count($tokens) - 1;
308
        }
309
        return 0;
310
    }
311
312
    /**
313
     * @param array $lines
314
     */
315
    protected function decodeGetUniqueId($lines)
316
    {
317
        $uids = [];
318
        foreach (array_map([$this, 'decodeLine'], $lines) as $tokens) {
319
            if (!isset($tokens[1]) || $tokens[1] !== 'FETCH') {
320
                continue;
321
            }
322 View Code Duplication
            if (!isset($tokens[2][0]) || $tokens[2][0] !== 'UID') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
323
                continue;
324
            }
325
            if (!isset($tokens[0]) || !isset($tokens[2][1])) {
326
                continue;
327
            }
328
            $uids[$tokens[0]] = $tokens[2][1];
329
        }
330
        return $uids;
331
    }
332
333
    /*
334
     * @param array $lines
335
     */
336
    protected function decodeSize($lines)
337
    {
338
        $sizes = [];
339
        foreach (array_map([$this, 'decodeLine'], $lines) as $tokens) {
340
            if (!isset($tokens[1]) || $tokens[1] !== 'FETCH') {
341
                continue;
342
            }
343 View Code Duplication
            if (!isset($tokens[2][0]) || $tokens[2][0] !== 'UID') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
344
                continue;
345
            }
346 View Code Duplication
            if (!isset($tokens[2][2]) || $tokens[2][2] !== 'RFC822.SIZE') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
347
                continue;
348
            }
349 View Code Duplication
            if (!isset($tokens[2][1]) || !isset($tokens[2][3])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
350
                continue;
351
            }
352
            $sizes[$tokens[2][1]] = $tokens[2][3];
353
        }
354
        return $sizes;
355
    }
356
357
    /**
358
     *
359
     * @param string    $tag response tag
360
     * @param string    $type OK, NO, BAD
361
     * @param string    $line last response line
362
     * @param array     $lines full response
363
     * @param string    $blob
364
     */
365
    protected function onCommand($tag, $type, $line, $lines, $blob)
366
    {
367
        $ok = $type === 'OK';
368
        $no = $type === 'NO';
369
        if ($type === 'BAD') {
370
            $this->log("Server said: " . $line);
371
        }
372
        $raw = ['lines' => $lines, 'blob' => $blob];
373
        switch ($tag) {
374
            case self::TAG_LOGIN:
375
                if ($ok) {
376
                    $this->state = self::STATE_AUTHORIZED;
377
                    $this->onResponse->executeOne($this, $line);
378
                } elseif ($no) {
379
                    $this->log("Failed to login: " . $line);
380
                    $this->finish();
381
                }
382
                break;
383
384
            case self::TAG_LIST:
385
                $this->onResponse->executeOne($this, $ok, $this->decodeList($lines));
386
                break;
387
388
            case self::TAG_GETUID:
389
                $this->onResponse->executeOne($this, $ok, $this->decodeGetUniqueId($lines));
390
                break;
391
392
            case self::TAG_COUNT:
393
                $this->onResponse->executeOne($this, $ok, $this->decodeCount($lines));
394
                break;
395
396
            case self::TAG_SIZE:
397
                $this->onResponse->executeOne($this, $ok, $this->decodeSize($lines));
398
                break;
399
400
            case self::TAG_DELETEMESSAGE:
401
                $this->expunge();
402
                break;
403
404
            case self::TAG_EXPUNGE:
405
                $this->onResponse->executeOne($this, count($lines) - 1, $raw);
406
                break;
407
408
            case self::TAG_GETRAWMESSAGE:
409
                $this->onResponse->executeOne($this, !empty($blob), $raw);
410
                break;
411
412
            case self::TAG_GETRAWHEADER:
413
                $this->onResponse->executeOne($this, !empty($blob), $raw);
414
                break;
415
416
            case self::TAG_GETRAWCONTENT:
417
                $this->onResponse->executeOne($this, !empty($blob), $raw);
418
                break;
419
420
            default:
421
                $this->onResponse->executeOne($this, $ok, $raw);
422
                break;
423
        }
424
    }
425
426
    public function onRead()
427
    {
428
        while (($rawLine = $this->readLine(\EventBuffer::EOL_CRLF_STRICT)) !== null) {
429
            if ($this->blobOctetsLeft > 0) {
430
                $this->blob .= $rawLine . "\r\n";
431
                $this->blobOctetsLeft -= strlen($rawLine) + 2;
432
                continue;
433
            }
434
            if (preg_match('~\{([0-9]+)\}$~', $rawLine, $matches)) {
435
                $this->blob = '';
436
                $this->blobOctetsLeft = $matches[1];
0 ignored issues
show
Documentation Bug introduced by
The property $blobOctetsLeft was declared of type integer, but $matches[1] is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
437
            }
438
            @list($tag, $line) = @explode(' ', $rawLine, 2);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
439
            @list($type, $restLine) = @explode(' ', $line, 2);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
440
441
            if ($this->state == self::STATE_CONNECTING) {
442
                if ($this->startsWith($rawLine, '* OK')) {
443
                    $this->state = self::STATE_CONNECTED;
444
                    $this->connected = true;
445
                } else {
446
                    $this->log("IMAP hello failed");
447
                    $this->finish();
448
                    return;
449
                }
450
                if ($this->onConnected) {
451
                    $this->onConnected->executeAll($this->connected ? $this : false);
452
                    $this->onConnected = null;
453
                }
454
                return;
455
            } elseif ($this->state != self::STATE_CONNECTING) {
456
                if ($tag === '*') {
457
                    $this->lines[] = $line;
458
                    continue;
459
                }
460
                if (!in_array($type, ['OK', 'BAD', 'NO'])) {
461
                    $this->lines[] = $rawLine;
462
                    continue;
463
                }
464
                $this->lines[] = $line;
465
                $this->onCommand($tag, $type, $line, $this->lines, $this->blob);
466
                $this->lines = [];
467
            }
468
        }
469
    }
470
471
    /**
472
     * Count messages all messages in current box
473
     * @param null $flags
474
     */
475
    public function countMessages($cb, $flags = null)
476
    {
477
        $this->onResponse->push($cb);
478
        if ($flags === null) {
479
            $this->searchMessages(['ALL'], self::TAG_COUNT);
480
            return;
481
        }
482
        $params = [];
483
        foreach ((array) $flags as $flag) {
484
            if (isset($this->searchFlags[$flag])) {
485
                $params[] = $this->searchFlags[$flag];
486
            } else {
487
                $params[] = 'KEYWORD';
488
                $params[] = $this->escapeString($flag);
489
            }
490
        }
491
        $this->searchMessages($params, self::TAG_COUNT);
492
    }
493
494
    /**
495
     * get a list of messages with number and size
496
     * @param int $uid number of message
0 ignored issues
show
Documentation introduced by
Should the type for parameter $uid not be integer|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
497
     */
498 View Code Duplication
    public function getSize($cb, $uid = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
499
    {
500
        $this->onResponse->push($cb);
501
        if ($uid) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $uid of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
502
            $this->fetch('RFC822.SIZE', $uid, null, true, self::TAG_SIZE);
0 ignored issues
show
Documentation introduced by
'RFC822.SIZE' is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
503
        } else {
504
            $this->fetch('RFC822.SIZE', 1, INF, true, self::TAG_SIZE);
0 ignored issues
show
Documentation introduced by
'RFC822.SIZE' is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
505
        }
506
    }
507
508
    /**
509
     * Fetch a message
510
     * @param int $uid unique number of message
511
     */
512
    public function getRawMessage($cb, $uid, $byUid = true)
513
    {
514
        $this->onResponse->push($cb);
515
        $this->fetch(['FLAGS', 'BODY[]'], $uid, null, $byUid, self::TAG_GETRAWMESSAGE);
516
    }
517
518
    /*
519
     * Get raw header of message or part
520
     * @param  int  $uid unique number of message
521
     */
522
    public function getRawHeader($cb, $uid, $byUid = true)
523
    {
524
        $this->onResponse->push($cb);
525
        $this->fetch(['FLAGS', 'RFC822.HEADER'], $uid, null, $byUid, self::TAG_GETRAWHEADER);
526
    }
527
528
    /*
529
     * Get raw content of message or part
530
     *
531
     * @param  int $uid   number of message
532
     */
533
    public function getRawContent($cb, $uid, $byUid = true)
534
    {
535
        $this->onResponse->push($cb);
536
        $this->fetch(['FLAGS', 'RFC822.TEXT'], $uid, null, $byUid, self::TAG_GETRAWCONTENT);
537
    }
538
539
    /**
540
     * get unique id for one or all messages
541
     *
542
     * @param int|null $id message number
543
     */
544 View Code Duplication
    public function getUniqueId($cb, $id = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
545
    {
546
        $this->onResponse->push($cb);
547
        if ($id) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $id of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
548
            $this->fetch('UID', $id, null, false, self::TAG_GETUID);
0 ignored issues
show
Documentation introduced by
'UID' is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
549
        } else {
550
            $this->fetch('UID', 1, INF, false, self::TAG_GETUID);
0 ignored issues
show
Documentation introduced by
'UID' is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
551
        }
552
    }
553
554
    /**
555
     * create a new folder (and parent folders if needed)
556
     *
557
     * @param string $folder folder name
558
     * @return bool success
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
559
     */
560
    public function createFolder($cb, $folder, $parentFolder = null)
561
    {
562
        $this->onResponse->push($cb);
563
        if ($parentFolder) {
564
            $folder =  $parentFolder . '/' . $folder ;
565
        }
566
        $this->writeln(self::TAG_CREATEFOLDER . " CREATE ".$this->escapeString($folder));
567
    }
568
569
    /**
570
     * remove a folder
571
     *
572
     * @param  string $name name or instance of folder
0 ignored issues
show
Bug introduced by
There is no parameter named $name. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
573
     */
574
    public function removeFolder($cb, $folder)
575
    {
576
        $this->onResponse->push($cb);
577
        $this->writeln(self::TAG_DELETEFOLDER . " DELETE ".$this->escapeString($folder));
578
    }
579
580
    /**
581
     * rename and/or move folder
582
     * @param  string $oldName name or instance of folder
583
     * @param  string $newName new global name of folder
584
     */
585
    public function renameFolder($cb, $oldName, $newName)
586
    {
587
        $this->onResponse->push($cb);
588
        $this->writeln(self::TAG_RENAMEFOLDER . " RENAME "
589
            .$this->escapeString($oldName)." ".$this->escapeString($newName));
590
    }
591
592
    /**
593
     * Remove a message from server.
594
     * @param  int $uid unique number of message
595
     */
596
    public function removeMessage($cb, $uid)
597
    {
598
        $this->onResponse->push($cb);
599
        $this->store([self::FLAG_DELETED], $uid, null, '+', true, self::TAG_DELETEMESSAGE);
600
    }
601
602
    /**
603
     * logout of imap server
604
     */
605
    public function logout($cb = null)
606
    {
607
        if ($cb) {
608
            $this->onResponse->push($cb);
609
        }
610
        $this->writeln(self::TAG_LOGOUT . " LOGOUT");
611
    }
612
613
    public function onFinish()
614
    {
615
        $this->onResponse->executeAll(false);
616
        parent::onFinish();
617
    }
618
}
619