Completed
Push — master ( 35c2da...82ac9f )
by Malte
02:51
created

Client::getConnection()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
<?php
2
/*
3
* File:     Client.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\IMAP;
14
15
use Webklex\IMAP\Exceptions\ConnectionFailedException;
16
use Webklex\IMAP\Exceptions\GetMessagesFailedException;
17
use Webklex\IMAP\Exceptions\MessageSearchValidationException;
18
use Webklex\IMAP\Support\FolderCollection;
19
use Webklex\IMAP\Support\MessageCollection;
20
21
/**
22
 * Class Client
23
 *
24
 * @package Webklex\IMAP
25
 */
26
class Client {
27
28
    /**
29
     * @var boolean|resource
30
     */
31
    public $connection = false;
32
33
    /**
34
     * Server hostname.
35
     *
36
     * @var string
37
     */
38
    public $host;
39
40
    /**
41
     * Server port.
42
     *
43
     * @var int
44
     */
45
    public $port;
46
47
    /**
48
     * Server encryption.
49
     * Supported: none, ssl or tls.
50
     *
51
     * @var string
52
     */
53
    public $encryption;
54
55
    /**
56
     * If server has to validate cert.
57
     *
58
     * @var mixed
59
     */
60
    public $validate_cert;
61
62
    /**
63
     * Account username/
64
     *
65
     * @var mixed
66
     */
67
    public $username;
68
69
    /**
70
     * Account password.
71
     *
72
     * @var string
73
     */
74
    public $password;
75
76
    /**
77
     * Read only parameter.
78
     *
79
     * @var bool
80
     */
81
    protected $read_only = false;
82
83
    /**
84
     * Active folder.
85
     *
86
     * @var Folder
87
     */
88
    protected $activeFolder = false;
89
90
    /**
91
     * Connected parameter
92
     *
93
     * @var bool
94
     */
95
    protected $connected = false;
96
97
    /**
98
     * IMAP errors that might have ben occurred
99
     *
100
     * @var array $errors
101
     */
102
    protected $errors = [];
103
104
    /**
105
     * All valid and available account config parameters
106
     *
107
     * @var array $validConfigKeys
108
     */
109
    protected $validConfigKeys = ['host', 'port', 'encryption', 'validate_cert', 'username', 'password'];
110
111
    /**
112
     * Client constructor.
113
     *
114
     * @param array $config
115
     */
116
    public function __construct($config = []) {
117
        $this->setConfig($config);
118
    }
119
120
    /**
121
     * Client destructor
122
     */
123
    public function __destruct() {
124
        $this->disconnect();
125
    }
126
127
    /**
128
     * Set the Client configuration
129
     *
130
     * @param array $config
131
     *
132
     * @return self
133
     */
134
    public function setConfig(array $config) {
135
        $defaultAccount = config('imap.default');
136
        $defaultConfig  = config("imap.accounts.$defaultAccount");
137
138
        foreach ($this->validConfigKeys as $key) {
139
            $this->$key = isset($config[$key]) ? $config[$key] : $defaultConfig[$key];
140
        }
141
142
        return $this;
143
    }
144
145
    /**
146
     * Get the current imap resource
147
     *
148
     * @return resource|boolean
149
     */
150
    public function getConnection() {
151
        return $this->connection;
152
    }
153
154
    /**
155
     * Set read only property and reconnect if it's necessary.
156
     *
157
     * @param bool $readOnly
158
     *
159
     * @return self
160
     */
161
    public function setReadOnly($readOnly = true) {
162
        $this->read_only = $readOnly;
163
164
        return $this;
165
    }
166
167
    /**
168
     * Determine if connection was established.
169
     *
170
     * @return bool
171
     */
172
    public function isConnected() {
173
        return $this->connected;
174
    }
175
176
    /**
177
     * Determine if connection is in read only mode.
178
     *
179
     * @return bool
180
     */
181
    public function isReadOnly() {
182
        return $this->read_only;
183
    }
184
185
    /**
186
     * Determine if connection was established and connect if not.
187
     *
188
     * @throws ConnectionFailedException
189
     */
190
    public function checkConnection() {
191
        if (!$this->isConnected() || $this->connection === false) {
192
            $this->connect();
193
        }
194
    }
195
196
    /**
197
     * Connect to server.
198
     *
199
     * @param int $attempts
200
     *
201
     * @return $this
202
     * @throws ConnectionFailedException
203
     */
204
    public function connect($attempts = 3) {
205
        $this->disconnect();
206
207
        try {
208
            $this->connection = imap_open(
209
                $this->getAddress(),
210
                $this->username,
211
                $this->password,
212
                $this->getOptions(),
213
                $attempts,
214
                config('imap.options.open')
215
            );
216
            $this->connected = !!$this->connection;
217
        } catch (\ErrorException $e) {
218
            $errors = imap_errors();
219
            $message = $e->getMessage().'. '.implode("; ", (is_array($errors) ? $errors : array()));
0 ignored issues
show
introduced by
The condition is_array($errors) is always true.
Loading history...
220
221
            throw new ConnectionFailedException($message);
222
        }
223
224
        return $this;
225
    }
226
227
    /**
228
     * Disconnect from server.
229
     *
230
     * @return $this
231
     */
232
    public function disconnect() {
233
        if ($this->isConnected() && $this->connection !== false) {
234
            $this->errors = array_merge($this->errors, imap_errors() ?: []);
235
            $this->connected = !imap_close($this->connection, CL_EXPUNGE);
0 ignored issues
show
Bug introduced by
It seems like $this->connection can also be of type true; however, parameter $imap_stream of imap_close() does only seem to accept resource, 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

235
            $this->connected = !imap_close(/** @scrutinizer ignore-type */ $this->connection, CL_EXPUNGE);
Loading history...
236
        }
237
238
        return $this;
239
    }
240
241
    /**
242
     * Get a folder instance by a folder name
243
     * ---------------------------------------------
244
     * PLEASE NOTE: This is an experimental function
245
     * ---------------------------------------------
246
     * @param string        $folder_name
247
     * @param int           $attributes
248
     * @param null|string   $delimiter
249
     *
250
     * @return Folder
251
     */
252
    public function getFolder($folder_name, $attributes = 32, $delimiter = null) {
253
254
        $delimiter = $delimiter === null ? config('imap.options.delimiter', '/') : $delimiter;
255
256
        $oFolder = new Folder($this, (object) [
257
            'name'       => $this->getAddress().$folder_name,
258
            'attributes' => $attributes,
259
            'delimiter'  => $delimiter
260
        ]);
261
262
        return $oFolder;
263
    }
264
265
    /**
266
     * Get folders list.
267
     * If hierarchical order is set to true, it will make a tree of folders, otherwise it will return flat array.
268
     *
269
     * @param boolean     $hierarchical
270
     * @param string|null $parent_folder
271
     *
272
     * @return FolderCollection
273
     * @throws ConnectionFailedException
274
     */
275
    public function getFolders($hierarchical = true, $parent_folder = null) {
276
        $this->checkConnection();
277
        $folders = FolderCollection::make([]);
278
279
        $pattern = $parent_folder.($hierarchical ? '%' : '*');
280
281
        $items = imap_getmailboxes($this->connection, $this->getAddress(), $pattern);
0 ignored issues
show
Bug introduced by
It seems like $this->connection can also be of type true; however, parameter $imap_stream of imap_getmailboxes() does only seem to accept resource, 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

281
        $items = imap_getmailboxes(/** @scrutinizer ignore-type */ $this->connection, $this->getAddress(), $pattern);
Loading history...
282
        foreach ($items as $item) {
283
            $folder = new Folder($this, $item);
284
285
            if ($hierarchical && $folder->hasChildren()) {
286
                $pattern = $folder->fullName.$folder->delimiter.'%';
287
288
                $children = $this->getFolders(true, $pattern);
289
                $folder->setChildren($children);
290
            }
291
292
            $folders->push($folder);
293
        }
294
295
        return $folders;
296
    }
297
298
    /**
299
     * Open folder.
300
     *
301
     * @param Folder $folder
302
     * @param int    $attempts
303
     *
304
     * @throws ConnectionFailedException
305
     */
306
    public function openFolder(Folder $folder, $attempts = 3) {
307
        $this->checkConnection();
308
309
        if ($this->activeFolder !== $folder) {
310
            $this->activeFolder = $folder;
311
312
            imap_reopen($this->connection, $folder->path, $this->getOptions(), $attempts);
0 ignored issues
show
Bug introduced by
It seems like $this->connection can also be of type true; however, parameter $imap_stream of imap_reopen() does only seem to accept resource, 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

312
            imap_reopen(/** @scrutinizer ignore-type */ $this->connection, $folder->path, $this->getOptions(), $attempts);
Loading history...
313
        }
314
    }
315
316
    /**
317
     * Create a new Folder
318
     * @param string $name
319
     *
320
     * @return bool
321
     * @throws ConnectionFailedException
322
     */
323
    public function createFolder($name) {
324
        $this->checkConnection();
325
        return imap_createmailbox($this->connection, $this->getAddress() . imap_utf7_encode($name));
0 ignored issues
show
Bug introduced by
It seems like $this->connection can also be of type true; however, parameter $imap_stream of imap_createmailbox() does only seem to accept resource, 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

325
        return imap_createmailbox(/** @scrutinizer ignore-type */ $this->connection, $this->getAddress() . imap_utf7_encode($name));
Loading history...
326
    }
327
    
328
    /**
329
     * Rename Folder
330
     * @param string $old_name
331
     * @param string $new_name
332
     *
333
     * @return bool
334
     * @throws ConnectionFailedException
335
     */
336
    public function renameFolder($old_name, $new_name) {
337
        $this->checkConnection();
338
        return imap_renamemailbox($this->connection, $this->getAddress() . imap_utf7_encode($old_name), $this->getAddress() . imap_utf7_encode($new_name));
0 ignored issues
show
Bug introduced by
It seems like $this->connection can also be of type true; however, parameter $imap_stream of imap_renamemailbox() does only seem to accept resource, 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

338
        return imap_renamemailbox(/** @scrutinizer ignore-type */ $this->connection, $this->getAddress() . imap_utf7_encode($old_name), $this->getAddress() . imap_utf7_encode($new_name));
Loading history...
339
    }
340
    
341
     /**
342
     * Delete Folder
343
     * @param string $name
344
     *
345
     * @return bool
346
     * @throws ConnectionFailedException
347
     */
348
    public function deleteFolder($name) {
349
        $this->checkConnection();
350
        return imap_deletemailbox($this->connection, $this->getAddress() . imap_utf7_encode($name));
0 ignored issues
show
Bug introduced by
It seems like $this->connection can also be of type true; however, parameter $imap_stream of imap_deletemailbox() does only seem to accept resource, 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

350
        return imap_deletemailbox(/** @scrutinizer ignore-type */ $this->connection, $this->getAddress() . imap_utf7_encode($name));
Loading history...
351
    }
352
353
    /**
354
     * Get messages from folder.
355
     *
356
     * @param Folder   $folder
357
     * @param string   $criteria
358
     * @param int|null $fetch_options
359
     * @param boolean  $fetch_body
360
     * @param boolean  $fetch_attachment
361
     *
362
     * @return MessageCollection
363
     * @throws ConnectionFailedException
364
     * @throws GetMessagesFailedException
365
     * @throws MessageSearchValidationException
366
     *
367
     * @deprecated 1.0.5.2:2.0.0 No longer needed. Use Folder::getMessages() instead
368
     * @see Folder::getMessages()
369
     */
370
    public function getMessages(Folder $folder, $criteria = 'ALL', $fetch_options = null, $fetch_body = true, $fetch_attachment = true) {
371
        return $folder->getMessages($criteria, $fetch_options, $fetch_body, $fetch_attachment);
372
    }
373
374
    /**
375
     * Get all unseen messages from folder
376
     *
377
     * @param Folder   $folder
378
     * @param string   $criteria
379
     * @param int|null $fetch_options
380
     * @param boolean  $fetch_body
381
     * @param boolean  $fetch_attachment
382
     *
383
     * @return MessageCollection
384
     * @throws ConnectionFailedException
385
     * @throws GetMessagesFailedException
386
     * @throws MessageSearchValidationException
387
     *
388
     * @deprecated 1.0.5:2.0.0 No longer needed. Use Folder::getMessages('UNSEEN') instead
389
     * @see Folder::getMessages()
390
     */
391
    public function getUnseenMessages(Folder $folder, $criteria = 'UNSEEN', $fetch_options = null, $fetch_body = true, $fetch_attachment = true) {
392
        return $folder->getUnseenMessages($criteria, $fetch_options, $fetch_body, $fetch_attachment);
0 ignored issues
show
Deprecated Code introduced by
The function Webklex\IMAP\Folder::getUnseenMessages() has been deprecated: 1.0.5:2.0.0 No longer needed. Use Folder::getMessages('UNSEEN') instead ( Ignorable by Annotation )

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

392
        return /** @scrutinizer ignore-deprecated */ $folder->getUnseenMessages($criteria, $fetch_options, $fetch_body, $fetch_attachment);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
393
    }
394
395
    /**
396
     * Search messages by a given search criteria
397
     *
398
     * @param array    $where
399
     * @param Folder   $folder
400
     * @param int|null $fetch_options
401
     * @param boolean  $fetch_body
402
     * @param string   $charset
403
     * @param boolean  $fetch_attachment
404
     *
405
     * @return MessageCollection
406
     * @throws ConnectionFailedException
407
     * @throws GetMessagesFailedException
408
     * @throws MessageSearchValidationException
409
     *
410
     * @deprecated 1.0.5:2.0.0 No longer needed. Use Folder::searchMessages() instead
411
     * @see Folder::searchMessages()
412
     *
413
     */
414
    public function searchMessages(array $where, Folder $folder, $fetch_options = null, $fetch_body = true, $charset = "UTF-8", $fetch_attachment = true) {
415
        return $folder->searchMessages($where, $fetch_options, $fetch_body, $charset, $fetch_attachment);
416
    }
417
418
    /**
419
     * Get option for imap_open and imap_reopen.
420
     * It supports only isReadOnly feature.
421
     *
422
     * @return int
423
     */
424
    protected function getOptions() {
425
        return ($this->isReadOnly()) ? OP_READONLY : 0;
426
    }
427
428
    /**
429
     * Get full address of mailbox.
430
     *
431
     * @return string
432
     */
433
    protected function getAddress() {
434
        $address = "{".$this->host.":".$this->port."/imap";
435
        if (!$this->validate_cert) {
436
            $address .= '/novalidate-cert';
437
        }
438
        if ($this->encryption == 'ssl') {
439
            $address .= '/ssl';
440
        }
441
        $address .= '}';
442
443
        return $address;
444
    }
445
446
    /**
447
     * Retrieve the quota level settings, and usage statics per mailbox
448
     *
449
     * @return array
450
     * @throws ConnectionFailedException
451
     */
452
    public function getQuota() {
453
        $this->checkConnection();
454
        return imap_get_quota($this->connection, 'user.'.$this->username);
0 ignored issues
show
Bug introduced by
It seems like $this->connection can also be of type true; however, parameter $imap_stream of imap_get_quota() does only seem to accept resource, 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

454
        return imap_get_quota(/** @scrutinizer ignore-type */ $this->connection, 'user.'.$this->username);
Loading history...
455
    }
456
457
    /**
458
     * Retrieve the quota settings per user
459
     *
460
     * @param string $quota_root
461
     *
462
     * @return array
463
     * @throws ConnectionFailedException
464
     */
465
    public function getQuotaRoot($quota_root = 'INBOX') {
466
        $this->checkConnection();
467
        return imap_get_quotaroot($this->connection, $quota_root);
0 ignored issues
show
Bug introduced by
It seems like $this->connection can also be of type true; however, parameter $imap_stream of imap_get_quotaroot() does only seem to accept resource, 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

467
        return imap_get_quotaroot(/** @scrutinizer ignore-type */ $this->connection, $quota_root);
Loading history...
468
    }
469
470
    /**
471
     * Gets the number of messages in the current mailbox
472
     *
473
     * @return int
474
     * @throws ConnectionFailedException
475
     */
476
    public function countMessages() {
477
        $this->checkConnection();
478
        return imap_num_msg($this->connection);
0 ignored issues
show
Bug introduced by
It seems like $this->connection can also be of type true; however, parameter $imap_stream of imap_num_msg() does only seem to accept resource, 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

478
        return imap_num_msg(/** @scrutinizer ignore-type */ $this->connection);
Loading history...
479
    }
480
481
    /**
482
     * Gets the number of recent messages in current mailbox
483
     *
484
     * @return int
485
     * @throws ConnectionFailedException
486
     */
487
    public function countRecentMessages() {
488
        $this->checkConnection();
489
        return imap_num_recent($this->connection);
0 ignored issues
show
Bug introduced by
It seems like $this->connection can also be of type true; however, parameter $imap_stream of imap_num_recent() does only seem to accept resource, 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

489
        return imap_num_recent(/** @scrutinizer ignore-type */ $this->connection);
Loading history...
490
    }
491
492
    /**
493
     * Returns all IMAP alert messages that have occurred
494
     *
495
     * @return array
496
     */
497
    public function getAlerts() {
498
        return imap_alerts();
499
    }
500
501
    /**
502
     * Returns all of the IMAP errors that have occurred
503
     *
504
     * @return array
505
     */
506
    public function getErrors() {
507
        $this->errors = array_merge($this->errors, imap_errors() ?: []);
508
509
        return $this->errors;
510
    }
511
512
    /**
513
     * Gets the last IMAP error that occurred during this page request
514
     *
515
     * @return string
516
     */
517
    public function getLastError() {
518
        return imap_last_error();
519
    }
520
521
    /**
522
     * Delete all messages marked for deletion
523
     *
524
     * @return bool
525
     * @throws ConnectionFailedException
526
     */
527
    public function expunge() {
528
        $this->checkConnection();
529
        return imap_expunge($this->connection);
0 ignored issues
show
Bug introduced by
It seems like $this->connection can also be of type true; however, parameter $imap_stream of imap_expunge() does only seem to accept resource, 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

529
        return imap_expunge(/** @scrutinizer ignore-type */ $this->connection);
Loading history...
530
    }
531
532
    /**
533
     * Check current mailbox
534
     *
535
     * @return object {
536
     *      Date    [string(37) "Wed, 8 Mar 2017 22:17:54 +0100 (CET)"]             current system time formatted according to » RFC2822
537
     *      Driver  [string(4) "imap"]                                              protocol used to access this mailbox: POP3, IMAP, NNTP
538
     *      Mailbox ["{[email protected]:993/imap/user="[email protected]"}INBOX"]    the mailbox name
539
     *      Nmsgs   [int(1)]                                                        number of messages in the mailbox
540
     *      Recent  [int(0)]                                                        number of recent messages in the mailbox
541
     * }
542
     * @throws ConnectionFailedException
543
     */
544
    public function checkCurrentMailbox() {
545
        $this->checkConnection();
546
        return imap_check($this->connection);
0 ignored issues
show
Bug introduced by
It seems like $this->connection can also be of type true; however, parameter $imap_stream of imap_check() does only seem to accept resource, 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

546
        return imap_check(/** @scrutinizer ignore-type */ $this->connection);
Loading history...
547
    }
548
}
549