Completed
Pull Request — master (#259)
by
unknown
04:18
created

Connection::searchMessages()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 3
c 1
b 0
f 1
nc 1
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
    public $eventList = [
58
        self::TAG_LOGIN         => 'onauth',
59
        self::TAG_LIST          => 'onlist',
60
        self::TAG_SELECT        => 'onselect',
61
        self::TAG_FETCH         => 'onfetch',
62
        self::TAG_SEARCH        => 'onsearch',
63
        self::TAG_COUNT         => 'oncount',
64
        self::TAG_SIZE          => 'onsize',
65
        self::TAG_GETRAWMESSAGE => 'onrawmessage',
66
        self::TAG_GETRAWHEADER  => 'onrawheader',
67
        self::TAG_GETRAWCONTENT => 'onrawcontent',
68
        self::TAG_GETUID        => 'ongetuid',
69
        self::TAG_CREATEFOLDER  => 'oncreatefolder',
70
        self::TAG_DELETEFOLDER  => 'ondeletefolder',
71
        self::TAG_RENAMEFOLDER  => 'onrenamefolder',
72
        self::TAG_STORE         => 'onstore',
73
        self::TAG_DELETEMESSAGE => 'ondeletemessage',
74
        self::TAG_EXPUNGE       => 'onexpunge',
75
        self::TAG_LOGOUT        => 'onlogout',
76
        self::TAG_STARTTLS      => 'onstarttls'
77
    ];
78
79
    protected $state;
80
    protected $lines = [];
81
    protected $blob = '';
82
    protected $blobOctetsLeft = 0;
83
84
    protected $bevConnectEnabled = false;
85
86
    public function onReady()
87
    {
88
        $this->state = self::STATE_CONNECTING;
89
    }
90
91
    /**
92
     * escape a single literal
93
     * @param  $string
94
     * @return string escaped list for imap
95
     */
96
    private function escapeString($string)
97
    {
98
        return '"' . str_replace(['\\', '"'], ['\\\\', '\\"'], $string) . '"';
99
    }
100
101
    /**
102
     * escape a list with literals or lists
103
     *
104
     * @param  array $list list with literals or lists as PHP array
105
     * @return string escaped list for imap
106
     */
107
    private function escapeList($list)
108
    {
109
        $result = [];
110
        foreach ($list as $v) {
111
            if (!is_array($v)) {
112
                $result[] = $v;
113
                continue;
114
            }
115
            $result[] = $this->escapeList($v);
116
        }
117
        return '(' . implode(' ', $result) . ')';
118
    }
119
120
    /**
121
     * split a given line in tokens. a token is literal of any form or a list
122
     *
123
     * @param  string $line line to decode
124
     * @return array tokens, literals are returned as string, lists as array
125
     */
126
    protected function decodeLine($line)
127
    {
128
        $tokens = [];
129
        $stack = [];
130
        /*
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...
131
            We start to decode the response here. The understood tokens are:
132
                literal
133
                "literal" or also "lit\\er\"al"
134
                (literals*)
135
            All tokens are returned in an array. Literals in braces (the last understood
136
            token in the list) are returned as an array of tokens. I.e. the following response:
137
                "foo" baz bar ("f\\\"oo" bar)
138
            would be returned as:
139
                array('foo', 'baz', 'bar', array('f\\\"oo', 'bar'));
140
        */
141
        //  replace any trailing <NL> including spaces with a single space
142
        $line = rtrim($line) . ' ';
143
        while (($pos = strpos($line, ' ')) !== false) {
144
            $token = substr($line, 0, $pos);
145
            if (!strlen($token)) {
146
                continue;
147
            }
148
            while ($token[0] == '(') {
149
                array_push($stack, $tokens);
150
                $tokens = [];
151
                $token = substr($token, 1);
152
            }
153
            if ($token[0] == '"') {
154
                if (preg_match('%^\(*"((.|\\\\|\\")*?)" *%', $line, $matches)) {
155
                    $tokens[] = $matches[1];
156
                    $line = substr($line, strlen($matches[0]));
157
                    continue;
158
                }
159
            }
160
            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...
161
                // closing braces are not separated by spaces, so we need to count them
162
                $braces = strlen($token);
163
                $token = rtrim($token, ')');
164
                // only count braces if more than one
165
                $braces -= strlen($token) + 1;
166
                // only add if token had more than just closing braces
167
                if (rtrim($token) != '') {
168
                    $tokens[] = rtrim($token);
169
                }
170
                $token = $tokens;
171
                $tokens = array_pop($stack);
172
                // special handline if more than one closing brace
173
                while ($braces-- > 0) {
174
                    $tokens[] = $token;
175
                    $token = $tokens;
176
                    $tokens = array_pop($stack);
177
                }
178
            }
179
            $tokens[] = $token;
180
            $line = substr($line, $pos + 1);
181
        }
182
        // maybe the server forgot to send some closing braces
183
        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...
184
            $child = $tokens;
185
            $tokens = array_pop($stack);
186
            $tokens[] = $child;
187
        }
188
        return $tokens;
189
    }
190
191
    /**
192
     * @param array $lines
193
     */
194
    protected function decodeLines($lines)
195
    {
196
        $tokenArray = [];
197
        foreach ($lines as $line) {
198
            $tokenArray[] = $this->decodeLine($line);
199
        }
200
        return $tokenArray;
201
    }
202
203
    /**
204
      * @param array $items
205
      * @param string $from
206
      * @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...
207
      * @param bool $uid
208
      * @param string $tag
209
     */
210
    public function fetch($items, $from, $to = null, $uid = false, $tag = self::TAG_FETCH)
211
    {
212
        if (is_array($from)) {
213
            $set = implode(',', $from);
214
        } elseif ($to === null) {
215
            $set = (int) $from;
216
        } elseif ($to === INF) {
217
            $set = (int) $from . ':*';
218
        } else {
219
            $set = (int) $from . ':' . (int) $to;
220
        }
221
        $query = $tag .($uid ? ' UID' : '')." FETCH $set ". $this->escapeList((array)$items) ."\r\n";
222
        $this->write($query);
223
    }
224
225
    /**
226
     * @param array $flags
227
     * @param string $from
228
     * @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...
229
     * @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...
230
     * @param bool $silent
231
     * @param string $tag
232
    */
233
    public function store(array $flags, $from, $to = null, $mode = null, $silent = true, $tag = self::TAG_STORE)
234
    {
235
        $item = 'FLAGS';
236
        if ($mode == '+' || $mode == '-') {
237
            $item = $mode . $item;
238
        }
239
        if ($silent) {
240
            $item .= '.SILENT';
241
        }
242
        $flags = $this->escapeList($flags);
243
        $set = (int)$from;
244
        if ($to !== null) {
245
            $set .= ':' . ($to == INF ? '*' : (int)$to);
246
        }
247
        $query = $tag . ' UID STORE ' . $set . ' ' . $item . ' ' . $flags ."\r\n";
248
        $this->write($query);
249
    }
250
251
    /**
252
    * @param string $reference
253
    * @param string $mailbox
254
    */
255
    public function listFolders($reference = '', $mailbox = '*')
256
    {
257
        $query = self::TAG_LIST .' LIST ' . $this->escapeString($reference)
258
            . ' ' .  $this->escapeString($mailbox) . "\r\n";
259
        $this->write($query);
260
    }
261
262
    /**
263
    * @param string $box
264
    */
265
    public function selectBox($box = 'INBOX')
266
    {
267
        $query = self::TAG_SELECT .' SELECT ' . $this->escapeString($box) . "\r\n";
268
        $this->write($query);
269
    }
270
271
    /**
272
    * @param string $tag
273
    */
274
    public function expunge($tag = self::TAG_EXPUNGE)
275
    {
276
        $query = $tag .' EXPUNGE' . "\r\n";
277
        $this->write($query);
278
    }
279
280
    /**
281
    * @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...
282
    * @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...
283
    */
284
    public function auth($login, $password)
285
    {
286
        $this->write(self::TAG_LOGIN . " LOGIN $login $password\r\n");
287
    }
288
289
    /**
290
     * @param array $params
291
     * @param string $tag
292
     */
293
    public function searchMessages($params, $tag = self::TAG_SEARCH)
294
    {
295
        $query = "$tag UID SEARCH ".implode(' ', $params)."\r\n";
296
        $this->write($query);
297
    }
298
299
    /**
300
     * @param string $haystack
301
     * @param string $needle
302
     */
303
    protected function startsWith($haystack, $needle)
304
    {
305
        // search backwards starting from haystack length characters from the end
306
        return $needle === "" || strrpos($haystack, $needle, -strlen($haystack)) !== false;
307
    }
308
309
    /**
310
     * @param array $lines
311
     */
312
    protected function decodeList($lines)
313
    {
314
        $list = [];
315
        foreach ($this->decodeLines($lines) as $tokens) {
316
            $folderEntry = [];
317
            if (empty($tokens[0]) || $tokens[0] !== 'LIST') {
318
                continue;
319
            }
320
            if (!empty($tokens[3])) {
321
                $folderEntry['name'] = $tokens[3];
322
            } else {
323
                continue;
324
            }
325
            if (!empty($tokens[1])) {
326
                $folderEntry['flags'] = $tokens[1];
327
            } else {
328
                continue;
329
            }
330
            $list[] = $folderEntry;
331
        }
332
        return $list;
333
    }
334
335
    /**
336
     * @param array $lines
337
     */
338
    protected function decodeCount($lines)
339
    {
340
        foreach ($this->decodeLines($lines) as $tokens) {
341
            if (empty($tokens[0]) || $tokens[0] !== 'SEARCH') {
342
                continue;
343
            }
344
            return count($tokens) - 1;
345
        }
346
        return 0;
347
    }
348
349
    /**
350
     * @param array $lines
351
     */
352
    protected function decodeGetUniqueId($lines)
353
    {
354
        $uids = [];
355
        foreach ($this->decodeLines($lines) as $tokens) {
356
            //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...
357
            if (empty($tokens[1]) || $tokens[1] !== 'FETCH') {
358
                continue;
359
            }
360 View Code Duplication
            if (empty($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...
361
                continue;
362
            }
363
            if (empty($tokens[0]) || empty($tokens[2][1])) {
364
                continue;
365
            }
366
            $uids[$tokens[0]] = $tokens[2][1];
367
        }
368
        return $uids;
369
    }
370
371
    /*
372
     * @param array $lines
373
     */
374
    protected function decodeSize($lines)
375
    {
376
        $sizes = [];
377
        foreach ($this->decodeLines($lines) as $tokens) {
378
            if (empty($tokens[1]) || $tokens[1] !== 'FETCH') {
379
                continue;
380
            }
381 View Code Duplication
            if (empty($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...
382
                continue;
383
            }
384 View Code Duplication
            if (empty($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...
385
                continue;
386
            }
387
            if (empty($tokens[2][1]) || empty($tokens[2][3])) {
388
                continue;
389
            }
390
            $sizes[$tokens[2][1]] = $tokens[2][3];
391
        }
392
        return $sizes;
393
    }
394
395
    /**
396
     *
397
     * @param string    $tag response tag
398
     * @param string    $type OK, NO, BAD
399
     * @param string    $line last response line
400
     * @param array     $lines full response
401
     * @param string    $blob
402
     */
403
    protected function onCommand($tag, $type, $line, $lines, $blob)
404
    {
405
        $ok = $type === 'OK';
406
        $no = $type === 'NO';
407
        if ($type === 'BAD') {
408
            Daemon::log("Server said: " . $line);
409
        }
410
        $raw = ['lines' => $lines, 'blob' => $blob];
411
        switch ($tag) {
412
            case self::TAG_LOGIN:
413
                if ($ok) {
414
                    $this->state = self::STATE_AUTHORIZED;
415
                    $this->event($this->eventList[$tag], $line);
416
                } elseif ($no) {
417
                    Daemon::log("Failed to login: " . $line);
418
                    $this->finish();
419
                }
420
                break;
421
422
            case self::TAG_LIST:
423
                $this->event($this->eventList[$tag], $ok, $this->decodeList($lines));
424
                break;
425
426
            case self::TAG_GETUID:
427
                $this->event($this->eventList[$tag], $ok, $this->decodeGetUniqueId($lines));
428
                break;
429
430
            case self::TAG_COUNT:
431
                $this->event($this->eventList[$tag], $ok, $this->decodeCount($lines));
432
                break;
433
434
            case self::TAG_SIZE:
435
                $this->event($this->eventList[$tag], $ok, $this->decodeSize($lines));
436
                break;
437
438
            case self::TAG_DELETEMESSAGE:
439
                $this->expunge();
440
                break;
441
442
            case self::TAG_EXPUNGE:
443
                $this->event('onremovemessage', count($lines) - 1, $raw);
444
                break;
445
446
            case self::TAG_GETRAWMESSAGE:
447
                $this->event($this->eventList[$tag], !empty($blob), $raw);
448
                break;
449
450
            case self::TAG_GETRAWHEADER:
451
                $this->event($this->eventList[$tag], !empty($blob), $raw);
452
                break;
453
454
            case self::TAG_GETRAWCONTENT:
455
                $this->event($this->eventList[$tag], !empty($blob), $raw);
456
                break;
457
458
            default:
459
                if (isset($this->eventList[$tag])) {
460
                    $this->event($this->eventList[$tag], $ok, $raw);
461
                }
462
                break;
463
        }
464
    }
465
466
    public function onRead()
467
    {
468
        while (($rawLine = $this->readLine(\EventBuffer::EOL_CRLF_STRICT)) !== null) {
469
            //Daemon::log("RAWLINE: #" . $rawLine . '#');
0 ignored issues
show
Unused Code Comprehensibility introduced by
47% 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...
470
            if ($this->blobOctetsLeft > 0) {
471
                $this->blob .= $rawLine . "\r\n";
472
                $this->blobOctetsLeft -= strlen($rawLine) + 2;
473
                continue;
474
            }
475
            if (preg_match('~\{([0-9]+)\}$~', $rawLine, $matches)) {
476
                $this->blob = '';
477
                $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...
478
            }
479
            @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...
480
            @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...
481
482
            if ($this->state == self::STATE_CONNECTING) {
483
                if ($this->startsWith($rawLine, '* OK')) {
484
                    $this->state = self::STATE_CONNECTED;
485
                    $this->connected = true;
486
                } else {
487
                    Daemon::log("IMAP hello failed");
488
                    $this->finish();
489
                    return;
490
                }
491
                if ($this->onConnected) {
492
                    $this->onConnected->executeAll($this->connected ? $this : false);
493
                    $this->onConnected = null;
494
                }
495
                return;
496
            } elseif ($this->state != self::STATE_CONNECTING) {
497
                if ($tag === '*') {
498
                    $this->lines[] = $line;
499
                    continue;
500
                }
501
                if (!in_array($type, ['OK', 'BAD', 'NO'])) {
502
                    $this->lines[] = $rawLine;
503
                    continue;
504
                }
505
                $this->lines[] = $line;
506
                $this->onCommand($tag, $type, $line, $this->lines, $this->blob);
507
                $this->lines = [];
508
            }
509
        }
510
    }
511
512
    /**
513
     * Count messages all messages in current box
514
     * @param null $flags
515
     */
516
    public function countMessages($flags = null)
517
    {
518
        if ($flags === null) {
519
            $this->searchMessages(['ALL'], self::TAG_COUNT);
520
            return;
521
        }
522
523
        $params = [];
524
        foreach ((array) $flags as $flag) {
525
            if (isset($this->searchFlags[$flag])) {
526
                $params[] = $this->searchFlags[$flag];
527
            } else {
528
                $params[] = 'KEYWORD';
529
                $params[] = $this->escapeString($flag);
530
            }
531
        }
532
533
        $this->searchMessages($params, self::TAG_COUNT);
534
    }
535
536
    /**
537
     * get a list of messages with number and size
538
     * @param int $uid number of message
539
     */
540 View Code Duplication
    public function getSize($uid = 0)
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...
541
    {
542
        if ($uid) {
543
            $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...
544
        } else {
545
            $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...
546
        }
547
    }
548
549
    /**
550
     * Fetch a message
551
     * @param int $uid unique number of message
552
     */
553
    public function getRawMessage($uid, $byUid = true)
554
    {
555
        $this->fetch(['FLAGS', 'BODY[]'], $uid, null, $byUid, self::TAG_GETRAWMESSAGE);
556
    }
557
558
    /*
559
     * Get raw header of message or part
560
     * @param  int  $uid unique number of message
561
     */
562
    public function getRawHeader($uid, $byUid = true)
563
    {
564
        $this->fetch(['FLAGS', 'RFC822.HEADER'], $uid, null, $byUid, self::TAG_GETRAWHEADER);
565
    }
566
567
    /*
568
     * Get raw content of message or part
569
     *
570
     * @param  int $uid   number of message
571
     */
572
    public function getRawContent($uid, $byUid = true)
573
    {
574
        $this->fetch(['FLAGS', 'RFC822.TEXT'], $uid, null, $byUid, self::TAG_GETRAWCONTENT);
575
    }
576
577
    /**
578
     * get unique id for one or all messages
579
     *
580
     * @param int|null $id message number
581
     */
582 View Code Duplication
    public function getUniqueId($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...
583
    {
584
        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...
585
            $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...
586
        } else {
587
            $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...
588
        }
589
    }
590
591
    /**
592
     * create a new folder (and parent folders if needed)
593
     *
594
     * @param string $folder folder name
595
     * @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...
596
     */
597
    public function createFolder($folder, $parentFolder = null)
598
    {
599
        if ($parentFolder) {
600
            $folder =  $parentFolder . '/' . $folder ;
601
        }
602
        $query = self::TAG_CREATEFOLDER . " CREATE ".$this->escapeString($folder)."\r\n";
603
        $this->write($query);
604
    }
605
606
    /**
607
     * remove a folder
608
     *
609
     * @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...
610
     */
611
    public function removeFolder($folder)
612
    {
613
        $query = self::TAG_DELETEFOLDER . " DELETE ".$this->escapeString($folder)."\r\n";
614
        $this->write($query);
615
    }
616
617
    /**
618
     * rename and/or move folder
619
     * @param  string $oldName name or instance of folder
620
     * @param  string $newName new global name of folder
621
     */
622
    public function renameFolder($oldName, $newName)
623
    {
624
        $query = self::TAG_RENAMEFOLDER . " RENAME "
625
            .$this->escapeString($oldName)." ".$this->escapeString($newName)."\r\n";
626
        $this->write($query);
627
    }
628
629
    /**
630
     * Remove a message from server.
631
     * @param  int $uid unique number of message
632
     */
633
    public function removeMessage($uid)
634
    {
635
        $this->store([self::FLAG_DELETED], $uid, null, '+', true, self::TAG_DELETEMESSAGE);
636
    }
637
638
    /**
639
     * logout of imap server
640
     */
641
    public function logout()
642
    {
643
        $query = self::TAG_LOGOUT . " LOGOUT\r\n";
644
        $this->write($query);
645
    }
646
647
    public function onFinish()
648
    {
649
        $this->onResponse->executeAll(false);
650
        parent::onFinish();
651
    }
652
}
653