Completed
Pull Request — master (#259)
by
unknown
07:04 queued 02:44
created

Connection::startsWith()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 1
Metric Value
cc 2
eloc 2
c 2
b 1
f 1
nc 2
nop 2
dl 0
loc 5
rs 9.4285
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 $state;
58
    protected $lines = [];
59
    protected $blob = '';
60
    protected $blobOctetsLeft = 0;
61
62
    protected $bevConnectEnabled = false;
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 '"' . str_replace(['\\', '"'], ['\\\\', '\\"'], $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
        /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
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 $lines
171
     */
172
    protected function decodeLines($lines)
173
    {
174
        $tokenArray = [];
175
        foreach ($lines as $line) {
176
            $tokenArray[] = $this->decodeLine($line);
177
        }
178
        return $tokenArray;
179
    }
180
181
    /**
182
      * @param array $items
183
      * @param string $from
184
      * @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...
185
      * @param bool $uid
186
      * @param string $tag
187
     */
188
    protected function fetch($items, $from, $to = null, $uid = false, $tag = self::TAG_FETCH)
189
    {
190
        if (is_array($from)) {
191
            $set = implode(',', $from);
192
        } elseif ($to === null) {
193
            $set = (int) $from;
194
        } elseif ($to === INF) {
195
            $set = (int) $from . ':*';
196
        } else {
197
            $set = (int) $from . ':' . (int) $to;
198
        }
199
        $this->write($tag .($uid ? ' UID' : '')." FETCH $set ". $this->escapeList((array)$items) ."\r\n");
200
    }
201
202
    /**
203
     * @param array $flags
204
     * @param string $from
205
     * @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...
206
     * @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...
207
     * @param bool $silent
208
     * @param string $tag
209
    */
210
    protected function store(array $flags, $from, $to = null, $mode = null, $silent = true, $tag = self::TAG_STORE)
211
    {
212
        $item = 'FLAGS';
213
        if ($mode == '+' || $mode == '-') {
214
            $item = $mode . $item;
215
        }
216
        if ($silent) {
217
            $item .= '.SILENT';
218
        }
219
        $flags = $this->escapeList($flags);
220
        $set = (int)$from;
221
        if ($to !== null) {
222
            $set .= ':' . ($to == INF ? '*' : (int)$to);
223
        }
224
        $this->write($tag . ' UID STORE ' . $set . ' ' . $item . ' ' . $flags ."\r\n");
225
    }
226
227
    /**
228
    * @param string $reference
229
    * @param string $mailbox
230
    */
231
    public function listFolders($cb, $reference = '', $mailbox = '*')
232
    {
233
        $this->onResponse->push($cb);
234
        $this->write(self::TAG_LIST .' LIST ' . $this->escapeString($reference)
235
            . ' ' .  $this->escapeString($mailbox) . "\r\n");
236
    }
237
238
    /**
239
    * @param string $box
240
    */
241
    public function selectBox($cb, $box = 'INBOX')
242
    {
243
        $this->onResponse->push($cb);
244
        $this->write(self::TAG_SELECT .' SELECT ' . $this->escapeString($box) . "\r\n");
245
    }
246
247
    /**
248
    * @param string $tag
249
    */
250
    protected function expunge($tag = self::TAG_EXPUNGE)
251
    {
252
        $this->write($tag .' EXPUNGE' . "\r\n");
253
    }
254
255
    /**
256
    * @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...
257
    * @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...
258
    */
259
    public function auth($cb, $login, $password)
260
    {
261
        $this->onResponse->push($cb);
262
        $this->write(self::TAG_LOGIN . " LOGIN $login $password\r\n");
263
    }
264
265
    /**
266
     * @param array $params
267
     * @param string $tag
268
     */
269
    protected function searchMessages($params, $tag = self::TAG_SEARCH)
270
    {
271
        $this->write("$tag UID SEARCH ".implode(' ', $params)."\r\n");
272
    }
273
274
    /**
275
     * @param string $haystack
276
     * @param string $needle
277
     */
278
    protected function startsWith($haystack, $needle)
279
    {
280
        // search backwards starting from haystack length characters from the end
281
        return $needle === '' || strrpos($haystack, $needle, -strlen($haystack)) !== false;
282
    }
283
284
    /**
285
     * @param array $lines
286
     */
287
    protected function decodeList($lines)
288
    {
289
        $list = [];
290
        foreach ($this->decodeLines($lines) as $tokens) {
291
            $folderEntry = [];
292
            if (!isset($tokens[0]) || $tokens[0] !== 'LIST') {
293
                continue;
294
            }
295
            if (isset($tokens[3])) {
296
                $folderEntry['name'] = $tokens[3];
297
            } else {
298
                continue;
299
            }
300
            if (isset($tokens[1])) {
301
                $folderEntry['flags'] = $tokens[1];
302
            } else {
303
                continue;
304
            }
305
            $list[] = $folderEntry;
306
        }
307
        return $list;
308
    }
309
310
    /**
311
     * @param array $lines
312
     */
313
    protected function decodeCount($lines)
314
    {
315
        foreach ($this->decodeLines($lines) as $tokens) {
316
            if (!isset($tokens[0]) || $tokens[0] !== 'SEARCH') {
317
                continue;
318
            }
319
            return count($tokens) - 1;
320
        }
321
        return 0;
322
    }
323
324
    /**
325
     * @param array $lines
326
     */
327
    protected function decodeGetUniqueId($lines)
328
    {
329
        $uids = [];
330
        foreach ($this->decodeLines($lines) as $tokens) {
331
            //Daemon::log(print_r($tokens, true));
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
332
            if (!isset($tokens[1]) || $tokens[1] !== 'FETCH') {
333
                continue;
334
            }
335 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...
336
                continue;
337
            }
338
            if (!isset($tokens[0]) || !isset($tokens[2][1])) {
339
                continue;
340
            }
341
            $uids[$tokens[0]] = $tokens[2][1];
342
        }
343
        return $uids;
344
    }
345
346
    /*
347
     * @param array $lines
348
     */
349
    protected function decodeSize($lines)
350
    {
351
        $sizes = [];
352
        foreach ($this->decodeLines($lines) as $tokens) {
353
            if (!isset($tokens[1]) || $tokens[1] !== 'FETCH') {
354
                continue;
355
            }
356 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...
357
                continue;
358
            }
359 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...
360
                continue;
361
            }
362 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...
363
                continue;
364
            }
365
            $sizes[$tokens[2][1]] = $tokens[2][3];
366
        }
367
        return $sizes;
368
    }
369
370
    /**
371
     *
372
     * @param string    $tag response tag
373
     * @param string    $type OK, NO, BAD
374
     * @param string    $line last response line
375
     * @param array     $lines full response
376
     * @param string    $blob
377
     */
378
    protected function onCommand($tag, $type, $line, $lines, $blob)
379
    {
380
        $ok = $type === 'OK';
381
        $no = $type === 'NO';
382
        if ($type === 'BAD') {
383
            Daemon::log("Server said: " . $line);
384
        }
385
        $raw = ['lines' => $lines, 'blob' => $blob];
386
        switch ($tag) {
387
            case self::TAG_LOGIN:
388
                if ($ok) {
389
                    $this->state = self::STATE_AUTHORIZED;
390
                    $this->onResponse->executeOne($this, $line);
391
                } elseif ($no) {
392
                    $this->log("Failed to login: " . $line);
393
                    $this->finish();
394
                }
395
                break;
396
397
            case self::TAG_LIST:
398
                $this->onResponse->executeOne($this, $ok, $this->decodeList($lines));
399
                break;
400
401
            case self::TAG_GETUID:
402
                $this->onResponse->executeOne($this, $ok, $this->decodeGetUniqueId($lines));
403
                break;
404
405
            case self::TAG_COUNT:
406
                $this->onResponse->executeOne($this, $ok, $this->decodeCount($lines));
407
                break;
408
409
            case self::TAG_SIZE:
410
                $this->onResponse->executeOne($this, $ok, $this->decodeSize($lines));
411
                break;
412
413
            case self::TAG_DELETEMESSAGE:
414
                $this->expunge();
415
                break;
416
417
            case self::TAG_EXPUNGE:
418
                $this->onResponse->executeOne($this, count($lines) - 1, $raw);
419
                break;
420
421
            case self::TAG_GETRAWMESSAGE:
422
                $this->onResponse->executeOne($this, !empty($blob), $raw);
423
                break;
424
425
            case self::TAG_GETRAWHEADER:
426
                $this->onResponse->executeOne($this, !empty($blob), $raw);
427
                break;
428
429
            case self::TAG_GETRAWCONTENT:
430
                $this->onResponse->executeOne($this, !empty($blob), $raw);
431
                break;
432
433
            default:
434
                $this->onResponse->executeOne($this, $ok, $raw);
435
                break;
436
        }
437
    }
438
439
    public function onRead()
440
    {
441
        while (($rawLine = $this->readLine(\EventBuffer::EOL_CRLF_STRICT)) !== null) {
442
            if ($this->blobOctetsLeft > 0) {
443
                $this->blob .= $rawLine . "\r\n";
444
                $this->blobOctetsLeft -= strlen($rawLine) + 2;
445
                continue;
446
            }
447
            if (preg_match('~\{([0-9]+)\}$~', $rawLine, $matches)) {
448
                $this->blob = '';
449
                $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...
450
            }
451
            @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...
452
            @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...
453
454
            if ($this->state == self::STATE_CONNECTING) {
455
                if ($this->startsWith($rawLine, '* OK')) {
456
                    $this->state = self::STATE_CONNECTED;
457
                    $this->connected = true;
458
                } else {
459
                    $this->log("IMAP hello failed");
460
                    $this->finish();
461
                    return;
462
                }
463
                if ($this->onConnected) {
464
                    $this->onConnected->executeAll($this->connected ? $this : false);
465
                    $this->onConnected = null;
466
                }
467
                return;
468
            } elseif ($this->state != self::STATE_CONNECTING) {
469
                if ($tag === '*') {
470
                    $this->lines[] = $line;
471
                    continue;
472
                }
473
                if (!in_array($type, ['OK', 'BAD', 'NO'])) {
474
                    $this->lines[] = $rawLine;
475
                    continue;
476
                }
477
                $this->lines[] = $line;
478
                $this->onCommand($tag, $type, $line, $this->lines, $this->blob);
479
                $this->lines = [];
480
            }
481
        }
482
    }
483
484
    /**
485
     * Count messages all messages in current box
486
     * @param null $flags
487
     */
488
    public function countMessages($cb, $flags = null)
489
    {
490
        $this->onResponse->push($cb);
491
        if ($flags === null) {
492
            $this->searchMessages(['ALL'], self::TAG_COUNT);
493
            return;
494
        }
495
        $params = [];
496
        foreach ((array) $flags as $flag) {
497
            if (isset($this->searchFlags[$flag])) {
498
                $params[] = $this->searchFlags[$flag];
499
            } else {
500
                $params[] = 'KEYWORD';
501
                $params[] = $this->escapeString($flag);
502
            }
503
        }
504
        $this->searchMessages($params, self::TAG_COUNT);
505
    }
506
507
    /**
508
     * get a list of messages with number and size
509
     * @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...
510
     */
511 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...
512
    {
513
        $this->onResponse->push($cb);
514
        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...
515
            $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...
516
        } else {
517
            $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...
518
        }
519
    }
520
521
    /**
522
     * Fetch a message
523
     * @param int $uid unique number of message
524
     */
525
    public function getRawMessage($cb, $uid, $byUid = true)
526
    {
527
        $this->onResponse->push($cb);
528
        $this->fetch(['FLAGS', 'BODY[]'], $uid, null, $byUid, self::TAG_GETRAWMESSAGE);
529
    }
530
531
    /*
532
     * Get raw header of message or part
533
     * @param  int  $uid unique number of message
534
     */
535
    public function getRawHeader($cb, $uid, $byUid = true)
536
    {
537
        $this->onResponse->push($cb);
538
        $this->fetch(['FLAGS', 'RFC822.HEADER'], $uid, null, $byUid, self::TAG_GETRAWHEADER);
539
    }
540
541
    /*
542
     * Get raw content of message or part
543
     *
544
     * @param  int $uid   number of message
545
     */
546
    public function getRawContent($cb, $uid, $byUid = true)
547
    {
548
        $this->onResponse->push($cb);
549
        $this->fetch(['FLAGS', 'RFC822.TEXT'], $uid, null, $byUid, self::TAG_GETRAWCONTENT);
550
    }
551
552
    /**
553
     * get unique id for one or all messages
554
     *
555
     * @param int|null $id message number
556
     */
557 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...
558
    {
559
        $this->onResponse->push($cb);
560
        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...
561
            $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...
562
        } else {
563
            $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...
564
        }
565
    }
566
567
    /**
568
     * create a new folder (and parent folders if needed)
569
     *
570
     * @param string $folder folder name
571
     * @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...
572
     */
573
    public function createFolder($cb, $folder, $parentFolder = null)
574
    {
575
        $this->onResponse->push($cb);
576
        if ($parentFolder) {
577
            $folder =  $parentFolder . '/' . $folder ;
578
        }
579
        $this->write(self::TAG_CREATEFOLDER . " CREATE ".$this->escapeString($folder)."\r\n");
580
    }
581
582
    /**
583
     * remove a folder
584
     *
585
     * @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...
586
     */
587
    public function removeFolder($cb, $folder)
588
    {
589
        $this->onResponse->push($cb);
590
        $this->write(self::TAG_DELETEFOLDER . " DELETE ".$this->escapeString($folder)."\r\n");
591
    }
592
593
    /**
594
     * rename and/or move folder
595
     * @param  string $oldName name or instance of folder
596
     * @param  string $newName new global name of folder
597
     */
598
    public function renameFolder($cb, $oldName, $newName)
599
    {
600
        $this->onResponse->push($cb);
601
        $this->write(self::TAG_RENAMEFOLDER . " RENAME "
602
            .$this->escapeString($oldName)." ".$this->escapeString($newName)."\r\n");
603
    }
604
605
    /**
606
     * Remove a message from server.
607
     * @param  int $uid unique number of message
608
     */
609
    public function removeMessage($cb, $uid)
610
    {
611
        $this->onResponse->push($cb);
612
        $this->store([self::FLAG_DELETED], $uid, null, '+', true, self::TAG_DELETEMESSAGE);
613
    }
614
615
    /**
616
     * logout of imap server
617
     */
618
    public function logout($cb = null)
619
    {
620
        if ($cb) {
621
            $this->onResponse->push($cb);
622
        }
623
        $this->write(self::TAG_LOGOUT . " LOGOUT\r\n");
624
    }
625
626
    public function onFinish()
627
    {
628
        $this->onResponse->executeAll(false);
629
        parent::onFinish();
630
    }
631
}
632