Issues (144)

src/Folder.php (1 issue)

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);
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);
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;
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);
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;
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);
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);
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
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();
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