Folder   A
last analyzed

Complexity

Total Complexity 37

Size/Duplication

Total Lines 446
Duplicated Lines 0 %

Importance

Changes 14
Bugs 3 Features 3
Metric Value
wmc 37
eloc 106
c 14
b 3
f 3
dl 0
loc 446
rs 9.44

22 Methods

Rating   Name   Duplication   Size   Complexity  
A messages() 0 2 1
A setDelimiter() 0 6 2
A getSimpleName() 0 4 1
B idle() 0 47 9
A delete() 0 8 2
A query() 0 6 2
A rename() 0 2 1
A overview() 0 5 2
A parseAttributes() 0 6 1
A subscribe() 0 3 1
A search() 0 2 1
A unsubscribe() 0 3 1
A getStatus() 0 2 1
A loadStatus() 0 4 1
A appendMessage() 0 12 2
A getClient() 0 2 1
A decodeName() 0 2 1
A hasChildren() 0 2 1
A examine() 0 3 2
A setChildren() 0 4 1
A move() 0 10 2
A __construct() 0 12 1
1
<?php
2
/*
3
* File:     Folder.php
4
* Category: -
5
* Author:   M. Goldenbaum
6
* Created:  19.01.17 22:21
7
* Updated:  -
8
*
9
* Description:
10
*  -
11
*/
12
13
namespace Webklex\PHPIMAP;
14
15
use Carbon\Carbon;
16
use Webklex\PHPIMAP\Exceptions\ConnectionFailedException;
17
use Webklex\PHPIMAP\Exceptions\NotSupportedCapabilityException;
18
use Webklex\PHPIMAP\Exceptions\RuntimeException;
19
use Webklex\PHPIMAP\Query\WhereQuery;
20
use Webklex\PHPIMAP\Support\FolderCollection;
21
use Webklex\PHPIMAP\Traits\HasEvents;
22
23
/**
24
 * Class Folder
25
 *
26
 * @package Webklex\PHPIMAP
27
 */
28
class Folder {
29
    use HasEvents;
30
31
    /**
32
     * Client instance
33
     *
34
     * @var Client
35
     */
36
    protected $client;
37
38
    /**
39
     * Folder full path
40
     *
41
     * @var string
42
     */
43
    public $path;
44
45
    /**
46
     * Folder name
47
     *
48
     * @var string
49
     */
50
    public $name;
51
52
    /**
53
     * Folder fullname
54
     *
55
     * @var string
56
     */
57
    public $full_name;
58
59
    /**
60
     * Children folders
61
     *
62
     * @var FolderCollection|array
63
     */
64
    public $children = [];
65
66
    /**
67
     * Delimiter for folder
68
     *
69
     * @var string
70
     */
71
    public $delimiter;
72
73
    /**
74
     * Indicates if folder can't containg any "children".
75
     * CreateFolder won't work on this folder.
76
     *
77
     * @var boolean
78
     */
79
    public $no_inferiors;
80
81
    /**
82
     * Indicates if folder is only container, not a mailbox - you can't open it.
83
     *
84
     * @var boolean
85
     */
86
    public $no_select;
87
88
    /**
89
     * Indicates if folder is marked. This means that it may contain new messages since the last time it was checked.
90
     * Not provided by all IMAP servers.
91
     *
92
     * @var boolean
93
     */
94
    public $marked;
95
96
    /**
97
     * Indicates if folder containg any "children".
98
     * Not provided by all IMAP servers.
99
     *
100
     * @var boolean
101
     */
102
    public $has_children;
103
104
    /**
105
     * Indicates if folder refers to other.
106
     * Not provided by all IMAP servers.
107
     *
108
     * @var boolean
109
     */
110
    public $referral;
111
112
    /** @var array */
113
    public $status;
114
115
    /**
116
     * Folder constructor.
117
     * @param Client $client
118
     * @param string $folder_name
119
     * @param string $delimiter
120
     * @param string[] $attributes
121
     */
122
    public function __construct(Client $client, string $folder_name, string $delimiter, array $attributes) {
123
        $this->client = $client;
124
125
        $this->events["message"] = $client->getDefaultEvents("message");
126
        $this->events["folder"] = $client->getDefaultEvents("folder");
127
128
        $this->setDelimiter($delimiter);
129
        $this->path      = $folder_name;
130
        $this->full_name  = $this->decodeName($folder_name);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->decodeName($folder_name) can also be of type array. However, the property $full_name is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
131
        $this->name      = $this->getSimpleName($this->delimiter, $this->full_name);
132
133
        $this->parseAttributes($attributes);
134
    }
135
136
    /**
137
     * Get a new search query instance
138
     * @param string[] $extensions
139
     *
140
     * @return WhereQuery
141
     * @throws Exceptions\ConnectionFailedException
142
     * @throws Exceptions\RuntimeException
143
     */
144
    public function query(array $extensions = []): WhereQuery {
145
        $this->getClient()->checkConnection();
146
        $this->getClient()->openFolder($this->path);
147
        $extensions = count($extensions) > 0 ? $extensions : $this->getClient()->extensions;
148
149
        return new WhereQuery($this->getClient(), $extensions);
0 ignored issues
show
Bug introduced by
It seems like $extensions can also be of type string; however, parameter $extensions of Webklex\PHPIMAP\Query\WhereQuery::__construct() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

149
        return new WhereQuery($this->getClient(), /** @scrutinizer ignore-type */ $extensions);
Loading history...
150
    }
151
152
    /**
153
     * Get a new search query instance
154
     * @param string[] $extensions
155
     *
156
     * @return WhereQuery
157
     * @throws Exceptions\ConnectionFailedException
158
     * @throws Exceptions\RuntimeException
159
     */
160
    public function search(array $extensions = []): WhereQuery {
161
        return $this->query($extensions);
162
    }
163
164
    /**
165
     * Get a new search query instance
166
     * @param string[] $extensions
167
     *
168
     * @return WhereQuery
169
     * @throws Exceptions\ConnectionFailedException
170
     * @throws Exceptions\RuntimeException
171
     */
172
    public function messages(array $extensions = []): WhereQuery {
173
        return $this->query($extensions);
174
    }
175
176
    /**
177
     * Determine if folder has children.
178
     *
179
     * @return bool
180
     */
181
    public function hasChildren(): bool {
182
        return $this->has_children;
183
    }
184
185
    /**
186
     * Set children.
187
     * @param FolderCollection|array $children
188
     *
189
     * @return self
190
     */
191
    public function setChildren($children = []): Folder {
192
        $this->children = $children;
193
194
        return $this;
195
    }
196
197
    /**
198
     * Decode name.
199
     * It converts UTF7-IMAP encoding to UTF-8.
200
     * @param $name
201
     *
202
     * @return array|false|string|string[]|null
203
     */
204
    protected function decodeName($name) {
205
        return mb_convert_encoding($name, "UTF-8", "UTF7-IMAP");
206
    }
207
208
    /**
209
     * Get simple name (without parent folders).
210
     * @param $delimiter
211
     * @param $full_name
212
     *
213
     * @return mixed
214
     */
215
    protected function getSimpleName($delimiter, $full_name) {
216
        $arr = explode($delimiter, $full_name);
217
218
        return end($arr);
219
    }
220
221
    /**
222
     * Parse attributes and set it to object properties.
223
     * @param $attributes
224
     */
225
    protected function parseAttributes($attributes) {
226
        $this->no_inferiors = in_array('\NoInferiors', $attributes);
227
        $this->no_select    = in_array('\NoSelect', $attributes);
228
        $this->marked       = in_array('\Marked', $attributes);
229
        $this->referral     = in_array('\Referral', $attributes);
230
        $this->has_children = in_array('\HasChildren', $attributes);
231
    }
232
233
    /**
234
     * Move or rename the current folder
235
     * @param string $new_name
236
     * @param boolean $expunge
237
     *
238
     * @return bool
239
     * @throws ConnectionFailedException
240
     * @throws Exceptions\EventNotFoundException
241
     * @throws Exceptions\FolderFetchingException
242
     * @throws Exceptions\RuntimeException
243
     */
244
    public function move(string $new_name, bool $expunge = true): bool {
245
        $this->client->checkConnection();
246
        $status = $this->client->getConnection()->renameFolder($this->full_name, $new_name);
247
        if($expunge) $this->client->expunge();
248
249
        $folder = $this->client->getFolder($new_name);
250
        $event = $this->getEvent("folder", "moved");
251
        $event::dispatch($this, $folder);
252
253
        return $status;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $status returns the type array which is incompatible with the type-hinted return boolean.
Loading history...
254
    }
255
256
    /**
257
     * Get a message overview
258
     * @param string|null $sequence uid sequence
259
     *
260
     * @return array
261
     * @throws ConnectionFailedException
262
     * @throws Exceptions\InvalidMessageDateException
263
     * @throws Exceptions\MessageNotFoundException
264
     * @throws Exceptions\RuntimeException
265
     */
266
    public function overview(string $sequence = null): array {
267
        $this->client->openFolder($this->path);
268
        $sequence = $sequence === null ? "1:*" : $sequence;
269
        $uid = ClientManager::get('options.sequence', IMAP::ST_MSGN) == IMAP::ST_UID;
270
        return $this->client->getConnection()->overview($sequence, $uid);
271
    }
272
273
    /**
274
     * Append a string message to the current mailbox
275
     * @param string $message
276
     * @param array|null $options
277
     * @param string|null|Carbon $internal_date
278
     *
279
     * @return bool
280
     * @throws Exceptions\ConnectionFailedException
281
     * @throws Exceptions\RuntimeException
282
     */
283
    public function appendMessage(string $message, array $options = null, $internal_date = null): bool {
284
        /**
285
         * Check if $internal_date is parsed. If it is null it should not be set. Otherwise, the message can't be stored.
286
         * If this parameter is set, it will set the INTERNALDATE on the appended message. The parameter should be a
287
         * date string that conforms to the rfc2060 specifications for a date_time value or be a Carbon object.
288
         */
289
290
        if ($internal_date instanceof Carbon){
291
            $internal_date = $internal_date->format('d-M-Y H:i:s O');
292
        }
293
294
        return $this->client->getConnection()->appendMessage($this->path, $message, $options, $internal_date);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->client->ge...ptions, $internal_date) returns the type array which is incompatible with the type-hinted return boolean.
Loading history...
295
    }
296
297
    /**
298
     * Rename the current folder
299
     * @param string $new_name
300
     * @param boolean $expunge
301
     *
302
     * @return bool
303
     * @throws ConnectionFailedException
304
     * @throws Exceptions\EventNotFoundException
305
     * @throws Exceptions\FolderFetchingException
306
     * @throws Exceptions\RuntimeException
307
     */
308
    public function rename(string $new_name, bool $expunge = true): bool {
309
        return $this->move($new_name, $expunge);
310
    }
311
312
    /**
313
     * Delete the current folder
314
     * @param boolean $expunge
315
     *
316
     * @return bool
317
     * @throws Exceptions\ConnectionFailedException
318
     * @throws Exceptions\RuntimeException
319
     * @throws Exceptions\EventNotFoundException
320
     */
321
    public function delete(bool $expunge = true): bool {
322
        $status = $this->client->getConnection()->deleteFolder($this->path);
323
        if($expunge) $this->client->expunge();
324
325
        $event = $this->getEvent("folder", "deleted");
326
        $event::dispatch($this);
327
328
        return $status;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $status returns the type array which is incompatible with the type-hinted return boolean.
Loading history...
329
    }
330
331
    /**
332
     * Subscribe the current folder
333
     *
334
     * @return bool
335
     * @throws Exceptions\ConnectionFailedException
336
     * @throws Exceptions\RuntimeException
337
     */
338
    public function subscribe(): bool {
339
        $this->client->openFolder($this->path);
340
        return $this->client->getConnection()->subscribeFolder($this->path);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->client->ge...ribeFolder($this->path) returns the type array which is incompatible with the type-hinted return boolean.
Loading history...
341
    }
342
343
    /**
344
     * Unsubscribe the current folder
345
     *
346
     * @return bool
347
     * @throws Exceptions\ConnectionFailedException
348
     * @throws Exceptions\RuntimeException
349
     */
350
    public function unsubscribe(): bool {
351
        $this->client->openFolder($this->path);
352
        return $this->client->getConnection()->unsubscribeFolder($this->path);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->client->ge...ribeFolder($this->path) returns the type array which is incompatible with the type-hinted return boolean.
Loading history...
353
    }
354
355
    /**
356
     * Idle the current connection
357
     * @param callable $callback
358
     * @param integer $timeout max 1740 seconds - recommended by rfc2177 §3. Should not be lower than the servers "* OK Still here" message interval
359
     * @param boolean $auto_reconnect try to reconnect on connection close (@deprecated is no longer required)
360
     *
361
     * @throws ConnectionFailedException
362
     * @throws Exceptions\InvalidMessageDateException
363
     * @throws Exceptions\MessageContentFetchingException
364
     * @throws Exceptions\MessageHeaderFetchingException
365
     * @throws Exceptions\RuntimeException
366
     * @throws Exceptions\EventNotFoundException
367
     * @throws Exceptions\MessageFlagException
368
     * @throws Exceptions\MessageNotFoundException
369
     * @throws Exceptions\NotSupportedCapabilityException
370
     */
371
    public function idle(callable $callback, int $timeout = 300, bool $auto_reconnect = false) {
0 ignored issues
show
Unused Code introduced by
The parameter $auto_reconnect is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

371
    public function idle(callable $callback, int $timeout = 300, /** @scrutinizer ignore-unused */ bool $auto_reconnect = false) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
372
        $this->client->setTimeout($timeout);
373
        if (!in_array("IDLE", $this->client->getConnection()->getCapabilities())) {
374
            throw new NotSupportedCapabilityException("IMAP server does not support IDLE");
375
        }
376
        $this->client->openFolder($this->path, true);
377
        $connection = $this->client->getConnection();
378
        $connection->idle();
379
380
        $sequence = ClientManager::get('options.sequence', IMAP::ST_MSGN);
381
382
        while (true) {
383
            try {
384
                // This polymorphic call is fine - Protocol::idle() will throw an exception beforehand
385
                $line = $connection->nextLine();
0 ignored issues
show
Bug introduced by
The method nextLine() does not exist on Webklex\PHPIMAP\Connecti...ocols\ProtocolInterface. It seems like you code against a sub-type of Webklex\PHPIMAP\Connecti...ocols\ProtocolInterface such as Webklex\PHPIMAP\Connection\Protocols\ImapProtocol. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

385
                /** @scrutinizer ignore-call */ 
386
                $line = $connection->nextLine();
Loading history...
Bug introduced by
The method nextLine() does not exist on Webklex\PHPIMAP\Connection\Protocols\Protocol. It seems like you code against a sub-type of Webklex\PHPIMAP\Connection\Protocols\Protocol such as Webklex\PHPIMAP\Connection\Protocols\ImapProtocol. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

385
                /** @scrutinizer ignore-call */ 
386
                $line = $connection->nextLine();
Loading history...
386
387
                if (($pos = strpos($line, "EXISTS")) !== false) {
388
                    $connection->done();
389
                    $msgn = (int) substr($line, 2, $pos -2);
390
391
                    $this->client->openFolder($this->path, true);
392
                    $message = $this->query()->getMessageByMsgn($msgn);
393
                    $message->setSequence($sequence);
394
                    $callback($message);
395
396
                    $event = $this->getEvent("message", "new");
397
                    $event::dispatch($message);
398
                    $connection->idle();
399
                } elseif (strpos($line, "OK") === false) {
400
                    $connection->done();
401
                    $connection->idle();
402
                }
403
            }catch (Exceptions\RuntimeException $e) {
404
                if(strpos($e->getMessage(), "empty response") >= 0 && $connection->connected()) {
405
                    $connection->done();
406
                    $connection->idle();
407
                    continue;
408
                }
409
                if(strpos($e->getMessage(), "connection closed") === false) {
410
                    throw $e;
411
                }
412
413
                $this->client->reconnect();
414
                $this->client->openFolder($this->path, true);
415
416
                $connection = $this->client->getConnection();
417
                $connection->idle();
418
            }
419
        }
420
    }
421
422
    /**
423
     * Get folder status information
424
     *
425
     * @return array
426
     * @throws Exceptions\ConnectionFailedException
427
     * @throws Exceptions\RuntimeException
428
     */
429
    public function getStatus(): array {
430
        return $this->examine();
431
    }
432
433
    /**
434
     * @throws RuntimeException
435
     * @throws ConnectionFailedException
436
     */
437
    public function loadStatus(): Folder
438
    {
439
        $this->status = $this->getStatus();
440
        return $this;
441
    }
442
443
    /**
444
     * Examine the current folder
445
     *
446
     * @return array
447
     * @throws Exceptions\ConnectionFailedException
448
     * @throws Exceptions\RuntimeException
449
     */
450
    public function examine(): array {
451
        $result = $this->client->getConnection()->examineFolder($this->path);
452
        return is_array($result) ? $result : [];
453
    }
454
455
    /**
456
     * Get the current Client instance
457
     *
458
     * @return Client
459
     */
460
    public function getClient(): Client {
461
        return $this->client;
462
    }
463
464
    /**
465
     * Set the delimiter
466
     * @param $delimiter
467
     */
468
    public function setDelimiter($delimiter){
469
        if(in_array($delimiter, [null, '', ' ', false]) === true) {
470
            $delimiter = ClientManager::get('options.delimiter', '/');
471
        }
472
473
        $this->delimiter = $delimiter;
474
    }
475
}
476