Completed
Push — master ( 4d0bad...6dffce )
by Malte
02:19
created

Client::getFolder()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 1
c 2
b 0
f 0
dl 0
loc 2
rs 10
cc 1
nc 1
nop 1
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\PHPIMAP;
14
15
use Webklex\PHPIMAP\Connection\Protocols\ImapProtocol;
16
use Webklex\PHPIMAP\Connection\Protocols\LegacyProtocol;
17
use Webklex\PHPIMAP\Connection\Protocols\Protocol;
18
use Webklex\PHPIMAP\Connection\Protocols\ProtocolInterface;
19
use Webklex\PHPIMAP\Exceptions\ConnectionFailedException;
20
use Webklex\PHPIMAP\Exceptions\FolderFetchingException;
21
use Webklex\PHPIMAP\Exceptions\MaskNotFoundException;
22
use Webklex\PHPIMAP\Exceptions\ProtocolNotSupportedException;
23
use Webklex\PHPIMAP\Support\FolderCollection;
24
use Webklex\PHPIMAP\Support\Masks\AttachmentMask;
25
use Webklex\PHPIMAP\Support\Masks\MessageMask;
26
use Webklex\PHPIMAP\Traits\HasEvents;
27
28
/**
29
 * Class Client
30
 *
31
 * @package Webklex\PHPIMAP
32
 */
33
class Client {
34
    use HasEvents;
35
36
    /**
37
     * Connection resource
38
     *
39
     * @var boolean|Protocol
40
     */
41
    public $connection = false;
42
43
    /**
44
     * Server hostname.
45
     *
46
     * @var string
47
     */
48
    public $host;
49
50
    /**
51
     * Server port.
52
     *
53
     * @var int
54
     */
55
    public $port;
56
57
    /**
58
     * Service protocol.
59
     *
60
     * @var int
61
     */
62
    public $protocol;
63
64
    /**
65
     * Server encryption.
66
     * Supported: none, ssl, tls, or notls.
67
     *
68
     * @var string
69
     */
70
    public $encryption;
71
72
    /**
73
     * If server has to validate cert.
74
     *
75
     * @var mixed
76
     */
77
    public $validate_cert;
78
79
    /**
80
     * Account username/
81
     *
82
     * @var mixed
83
     */
84
    public $username;
85
86
    /**
87
     * Account password.
88
     *
89
     * @var string
90
     */
91
    public $password;
92
93
    /**
94
     * Account authentication method.
95
     *
96
     * @var string
97
     */
98
    public $authentication;
99
100
    /**
101
     * Active folder.
102
     *
103
     * @var Folder
104
     */
105
    protected $active_folder = false;
106
107
    /**
108
     * Default message mask
109
     *
110
     * @var string $default_message_mask
111
     */
112
    protected $default_message_mask = MessageMask::class;
113
114
    /**
115
     * Default attachment mask
116
     *
117
     * @var string $default_attachment_mask
118
     */
119
    protected $default_attachment_mask = AttachmentMask::class;
120
121
    /**
122
     * Used default account values
123
     *
124
     * @var array $default_account_config
125
     */
126
    protected $default_account_config = [
127
        'host' => 'localhost',
128
        'port' => 993,
129
        'protocol'  => 'imap',
130
        'encryption' => 'ssl',
131
        'validate_cert' => true,
132
        'username' => '',
133
        'password' => '',
134
        'authentication' => null,
135
    ];
136
137
    /**
138
     * Client constructor.
139
     * @param array $config
140
     *
141
     * @throws MaskNotFoundException
142
     */
143
    public function __construct($config = []) {
144
        $this->setConfig($config);
145
        $this->setMaskFromConfig($config);
146
        $this->setEventsFromConfig($config);
147
    }
148
149
    /**
150
     * Client destructor
151
     */
152
    public function __destruct() {
153
        $this->disconnect();
154
    }
155
156
    /**
157
     * Set the Client configuration
158
     * @param array $config
159
     *
160
     * @return self
161
     */
162
    public function setConfig(array $config) {
163
        $default_account = ClientManager::get('default');
164
        $default_config  = ClientManager::get("accounts.$default_account");
165
166
        foreach ($this->default_account_config as $key => $value) {
167
            $this->setAccountConfig($key, $config, $default_config);
168
        }
169
170
        return $this;
171
    }
172
173
    /**
174
     * Set a specific account config
175
     * @param string $key
176
     * @param array $config
177
     * @param array $default_config
178
     */
179
    private function setAccountConfig($key, $config, $default_config){
180
        $value = $this->default_account_config[$key];
181
        if(isset($config[$key])) {
182
            $value = $config[$key];
183
        }elseif(isset($default_config[$key])) {
184
            $value = $default_config[$key];
185
        }
186
        $this->$key = $value;
187
    }
188
189
    /**
190
     * Look for a possible events in any available config
191
     * @param $config
192
     */
193
    protected function setEventsFromConfig($config) {
194
        $this->events = ClientManager::get("events");
195
        if(isset($config['events'])){
196
            if(isset($config['events'])) {
197
                foreach($config['events'] as $section => $events) {
198
                    $this->events[$section] = array_merge($this->events[$section], $events);
199
                }
200
            }
201
        }
202
    }
203
204
    /**
205
     * Look for a possible mask in any available config
206
     * @param $config
207
     *
208
     * @throws MaskNotFoundException
209
     */
210
    protected function setMaskFromConfig($config) {
211
        $default_config  = ClientManager::get("masks");
212
213
        if(isset($config['masks'])){
214
            if(isset($config['masks']['message'])) {
215
                if(class_exists($config['masks']['message'])) {
216
                    $this->default_message_mask = $config['masks']['message'];
217
                }else{
218
                    throw new MaskNotFoundException("Unknown mask provided: ".$config['masks']['message']);
219
                }
220
            }else{
221
                if(class_exists($default_config['message'])) {
222
                    $this->default_message_mask = $default_config['message'];
223
                }else{
224
                    throw new MaskNotFoundException("Unknown mask provided: ".$default_config['message']);
225
                }
226
            }
227
            if(isset($config['masks']['attachment'])) {
228
                if(class_exists($config['masks']['attachment'])) {
229
                    $this->default_message_mask = $config['masks']['attachment'];
230
                }else{
231
                    throw new MaskNotFoundException("Unknown mask provided: ".$config['masks']['attachment']);
232
                }
233
            }else{
234
                if(class_exists($default_config['attachment'])) {
235
                    $this->default_message_mask = $default_config['attachment'];
236
                }else{
237
                    throw new MaskNotFoundException("Unknown mask provided: ".$default_config['attachment']);
238
                }
239
            }
240
        }else{
241
            if(class_exists($default_config['message'])) {
242
                $this->default_message_mask = $default_config['message'];
243
            }else{
244
                throw new MaskNotFoundException("Unknown mask provided: ".$default_config['message']);
245
            }
246
247
            if(class_exists($default_config['attachment'])) {
248
                $this->default_message_mask = $default_config['attachment'];
249
            }else{
250
                throw new MaskNotFoundException("Unknown mask provided: ".$default_config['attachment']);
251
            }
252
        }
253
254
    }
255
256
    /**
257
     * Get the current imap resource
258
     *
259
     * @return bool|Protocol|ProtocolInterface
260
     * @throws ConnectionFailedException
261
     */
262
    public function getConnection() {
263
        $this->checkConnection();
264
        return $this->connection;
265
    }
266
267
    /**
268
     * Determine if connection was established.
269
     *
270
     * @return bool
271
     */
272
    public function isConnected() {
273
        return $this->connection ? $this->connection->connected() : false;
0 ignored issues
show
Bug introduced by
The method connected() does not exist on Webklex\PHPIMAP\Connection\Protocols\Protocol. Since it exists in all sub-types, consider adding an abstract or default implementation to Webklex\PHPIMAP\Connection\Protocols\Protocol. ( Ignorable by Annotation )

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

273
        return $this->connection ? $this->connection->/** @scrutinizer ignore-call */ connected() : false;
Loading history...
274
    }
275
276
    /**
277
     * Determine if connection was established and connect if not.
278
     *
279
     * @throws ConnectionFailedException
280
     */
281
    public function checkConnection() {
282
        if (!$this->isConnected()) {
283
            $this->connect();
284
        }
285
    }
286
287
    /**
288
     * Force a reconnect
289
     *
290
     * @throws ConnectionFailedException
291
     */
292
    public function reconnect() {
293
        if ($this->isConnected()) {
294
            $this->disconnect();
295
        }
296
        $this->connect();
297
    }
298
299
    /**
300
     * Connect to server.
301
     *
302
     * @return $this
303
     * @throws ConnectionFailedException
304
     */
305
    public function connect() {
306
        $this->disconnect();
307
        $protocol = strtolower($this->protocol);
308
309
        if ($protocol == "imap") {
310
            $timeout = $this->connection !== false ? $this->connection->getConnectionTimeout() : null;
311
            $this->connection = new ImapProtocol($this->validate_cert);
312
            $this->connection->setConnectionTimeout($timeout);
313
        }else{
314
            if (extension_loaded('imap') === false) {
315
                throw new ConnectionFailedException("connection setup failed", 0, new ProtocolNotSupportedException($protocol." is an unsupported protocol"));
316
            }
317
            $this->connection = new LegacyProtocol($this->validate_cert);
318
            if (strpos($protocol, "legacy-") === 0) {
319
                $protocol = substr($protocol, 7);
320
            }
321
            $this->connection->setProtocol($protocol);
322
        }
323
324
        $this->connection->connect($this->host, $this->port, $this->encryption);
0 ignored issues
show
Bug introduced by
$this->encryption of type string is incompatible with the type boolean expected by parameter $encryption of Webklex\PHPIMAP\Connecti...gacyProtocol::connect(). ( Ignorable by Annotation )

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

324
        $this->connection->connect($this->host, $this->port, /** @scrutinizer ignore-type */ $this->encryption);
Loading history...
325
        $this->authenticate();
326
327
        return $this;
328
    }
329
330
    /**
331
     * Authenticate the current session
332
     *
333
     * @throws ConnectionFailedException
334
     */
335
    protected function authenticate() {
336
        try {
337
            if ($this->authentication == "oauth") {
338
                $this->connection->authenticate($this->username, $this->password);
0 ignored issues
show
Bug introduced by
The method authenticate() does not exist on Webklex\PHPIMAP\Connection\Protocols\Protocol. Since it exists in all sub-types, consider adding an abstract or default implementation to Webklex\PHPIMAP\Connection\Protocols\Protocol. ( Ignorable by Annotation )

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

338
                $this->connection->/** @scrutinizer ignore-call */ 
339
                                   authenticate($this->username, $this->password);
Loading history...
339
            }else{
340
                $this->connection->login($this->username, $this->password);
0 ignored issues
show
Bug introduced by
The method login() does not exist on Webklex\PHPIMAP\Connection\Protocols\Protocol. Since it exists in all sub-types, consider adding an abstract or default implementation to Webklex\PHPIMAP\Connection\Protocols\Protocol. ( Ignorable by Annotation )

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

340
                $this->connection->/** @scrutinizer ignore-call */ 
341
                                   login($this->username, $this->password);
Loading history...
341
            }
342
        } catch (\Exception $e) {
343
            throw new ConnectionFailedException("connection setup failed", 0, $e);
344
        }
345
    }
346
347
    /**
348
     * Disconnect from server.
349
     *
350
     * @return $this
351
     */
352
    public function disconnect() {
353
        if ($this->isConnected() && $this->connection !== false) {
354
            $this->connection->logout();
0 ignored issues
show
Bug introduced by
The method logout() does not exist on Webklex\PHPIMAP\Connection\Protocols\Protocol. Since it exists in all sub-types, consider adding an abstract or default implementation to Webklex\PHPIMAP\Connection\Protocols\Protocol. ( Ignorable by Annotation )

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

354
            $this->connection->/** @scrutinizer ignore-call */ 
355
                               logout();
Loading history...
355
        }
356
357
        return $this;
358
    }
359
360
    /**
361
     * Get a folder instance by a folder name
362
     * @param $folder_name
363
     *
364
     * @return mixed
365
     * @throws ConnectionFailedException
366
     * @throws FolderFetchingException
367
     */
368
    public function getFolder($folder_name) {
369
        return $this->getFolderByName($folder_name);
370
    }
371
372
    /**
373
     * Get a folder instance by a folder name
374
     * @param $folder_name
375
     *
376
     * @return mixed
377
     * @throws ConnectionFailedException
378
     * @throws FolderFetchingException
379
     */
380
    public function getFolderByName($folder_name) {
381
        return $this->getFolders(false)->where("name", $folder_name)->first();
382
    }
383
384
    /**
385
     * Get a folder instance by a folder path
386
     * @param $folder_path
387
     *
388
     * @return mixed
389
     * @throws ConnectionFailedException
390
     * @throws FolderFetchingException
391
     */
392
    public function getFolderByPath($folder_path) {
393
        return $this->getFolders(false)->where("path", $folder_path)->first();
394
    }
395
396
    /**
397
     * Get folders list.
398
     * If hierarchical order is set to true, it will make a tree of folders, otherwise it will return flat array.
399
     *
400
     * @param boolean     $hierarchical
401
     * @param string|null $parent_folder
402
     *
403
     * @return FolderCollection
404
     * @throws ConnectionFailedException
405
     * @throws FolderFetchingException
406
     */
407
    public function getFolders($hierarchical = true, $parent_folder = null) {
408
        $this->checkConnection();
409
        $folders = FolderCollection::make([]);
410
411
        $pattern = $parent_folder.($hierarchical ? '%' : '*');
412
        $items = $this->connection->folders('', $pattern);
0 ignored issues
show
Bug introduced by
The method folders() does not exist on Webklex\PHPIMAP\Connection\Protocols\Protocol. Since it exists in all sub-types, consider adding an abstract or default implementation to Webklex\PHPIMAP\Connection\Protocols\Protocol. ( Ignorable by Annotation )

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

412
        /** @scrutinizer ignore-call */ 
413
        $items = $this->connection->folders('', $pattern);
Loading history...
413
414
        if(is_array($items)){
415
            foreach ($items as $folder_name => $item) {
416
                $folder = new Folder($this, $folder_name, $item["delimiter"], $item["flags"]);
417
418
                if ($hierarchical && $folder->hasChildren()) {
419
                    $pattern = $folder->full_name.$folder->delimiter.'%';
420
421
                    $children = $this->getFolders(true, $pattern);
422
                    $folder->setChildren($children);
423
                }
424
425
                $folders->push($folder);
426
            }
427
428
            return $folders;
429
        }else{
430
            throw new FolderFetchingException("failed to fetch any folders");
431
        }
432
    }
433
434
    /**
435
     * Open folder.
436
     * @param string $folder
437
     *
438
     * @return mixed
439
     * @throws ConnectionFailedException
440
     */
441
    public function openFolder($folder) {
442
        if ($this->active_folder == $folder && $this->isConnected()) {
0 ignored issues
show
introduced by
The condition $this->active_folder == $folder is always false.
Loading history...
443
            return true;
444
        }
445
        $this->checkConnection();
446
        $this->active_folder = $folder;
0 ignored issues
show
Documentation Bug introduced by
It seems like $folder of type string is incompatible with the declared type Webklex\PHPIMAP\Folder of property $active_folder.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
447
        return $this->connection->selectFolder($folder);
0 ignored issues
show
Bug introduced by
The method selectFolder() does not exist on Webklex\PHPIMAP\Connection\Protocols\Protocol. Since it exists in all sub-types, consider adding an abstract or default implementation to Webklex\PHPIMAP\Connection\Protocols\Protocol. ( Ignorable by Annotation )

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

447
        return $this->connection->/** @scrutinizer ignore-call */ selectFolder($folder);
Loading history...
448
    }
449
450
    /**
451
     * Create a new Folder
452
     * @param string $folder
453
     * @param boolean $expunge
454
     *
455
     * @return bool
456
     * @throws ConnectionFailedException
457
     * @throws FolderFetchingException
458
     * @throws Exceptions\EventNotFoundException
459
     */
460
    public function createFolder($folder, $expunge = true) {
461
        $this->checkConnection();
462
        $status = $this->connection->createFolder($folder);
0 ignored issues
show
Bug introduced by
The method createFolder() does not exist on Webklex\PHPIMAP\Connection\Protocols\Protocol. Since it exists in all sub-types, consider adding an abstract or default implementation to Webklex\PHPIMAP\Connection\Protocols\Protocol. ( Ignorable by Annotation )

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

462
        /** @scrutinizer ignore-call */ 
463
        $status = $this->connection->createFolder($folder);
Loading history...
463
        if($expunge) $this->expunge();
464
465
        $folder = $this->getFolder($folder);
466
        if($status && $folder) {
467
            $event = $this->getEvent("folder", "new");
468
            $event::dispatch($folder);
469
        }
470
471
        return $folder;
472
    }
473
474
    /**
475
     * Check a given folder
476
     * @param $folder
477
     *
478
     * @return false|object
479
     * @throws ConnectionFailedException
480
     */
481
    public function checkFolder($folder) {
482
        $this->checkConnection();
483
        return $this->connection->examineFolder($folder);
0 ignored issues
show
Bug introduced by
The method examineFolder() does not exist on Webklex\PHPIMAP\Connection\Protocols\Protocol. Since it exists in all sub-types, consider adding an abstract or default implementation to Webklex\PHPIMAP\Connection\Protocols\Protocol. ( Ignorable by Annotation )

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

483
        return $this->connection->/** @scrutinizer ignore-call */ examineFolder($folder);
Loading history...
484
    }
485
486
    /**
487
     * Get the current active folder
488
     *
489
     * @return Folder
490
     */
491
    public function getFolderPath(){
492
        return $this->active_folder;
493
    }
494
495
    /**
496
     * Retrieve the quota level settings, and usage statics per mailbox
497
     *
498
     * @return array
499
     * @throws ConnectionFailedException
500
     */
501
    public function getQuota() {
502
        $this->checkConnection();
503
        return $this->connection->getQuota($this->username);
0 ignored issues
show
Bug introduced by
The method getQuota() does not exist on Webklex\PHPIMAP\Connection\Protocols\Protocol. Since it exists in all sub-types, consider adding an abstract or default implementation to Webklex\PHPIMAP\Connection\Protocols\Protocol. ( Ignorable by Annotation )

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

503
        return $this->connection->/** @scrutinizer ignore-call */ getQuota($this->username);
Loading history...
504
    }
505
506
    /**
507
     * Retrieve the quota settings per user
508
     * @param string $quota_root
509
     *
510
     * @return array
511
     * @throws ConnectionFailedException
512
     */
513
    public function getQuotaRoot($quota_root = 'INBOX') {
514
        $this->checkConnection();
515
        return $this->connection->getQuotaRoot($quota_root);
0 ignored issues
show
Bug introduced by
The method getQuotaRoot() does not exist on Webklex\PHPIMAP\Connection\Protocols\Protocol. Since it exists in all sub-types, consider adding an abstract or default implementation to Webklex\PHPIMAP\Connection\Protocols\Protocol. ( Ignorable by Annotation )

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

515
        return $this->connection->/** @scrutinizer ignore-call */ getQuotaRoot($quota_root);
Loading history...
516
    }
517
518
    /**
519
     * Delete all messages marked for deletion
520
     *
521
     * @return bool
522
     * @throws ConnectionFailedException
523
     */
524
    public function expunge() {
525
        $this->checkConnection();
526
        return $this->connection->expunge();
0 ignored issues
show
Bug introduced by
The method expunge() does not exist on Webklex\PHPIMAP\Connection\Protocols\Protocol. Since it exists in all sub-types, consider adding an abstract or default implementation to Webklex\PHPIMAP\Connection\Protocols\Protocol. ( Ignorable by Annotation )

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

526
        return $this->connection->/** @scrutinizer ignore-call */ expunge();
Loading history...
527
    }
528
529
    /**
530
     * Set the imap timeout for a given operation type
531
     * @param $timeout
532
     *
533
     * @return Protocol
534
     */
535
    public function setTimeout($timeout) {
536
        return $this->connection->setConnectionTimeout($timeout);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->connection...ectionTimeout($timeout) also could return the type boolean which is incompatible with the documented return type Webklex\PHPIMAP\Connection\Protocols\Protocol.
Loading history...
537
    }
538
539
    /**
540
     * Get the timeout for a certain operation
541
     * @param $type
542
     *
543
     * @return int
544
     */
545
    public function getTimeout($type){
0 ignored issues
show
Unused Code introduced by
The parameter $type 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

545
    public function getTimeout(/** @scrutinizer ignore-unused */ $type){

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...
546
        return $this->connection->getConnectionTimeout();
547
    }
548
549
    /**
550
     * Get the default message mask
551
     *
552
     * @return string
553
     */
554
    public function getDefaultMessageMask(){
555
        return $this->default_message_mask;
556
    }
557
558
    /**
559
     * Get the default events for a given section
560
     * @param $section
561
     *
562
     * @return array
563
     */
564
    public function getDefaultEvents($section){
565
        return $this->events[$section];
566
    }
567
568
    /**
569
     * Set the default message mask
570
     * @param $mask
571
     *
572
     * @return $this
573
     * @throws MaskNotFoundException
574
     */
575
    public function setDefaultMessageMask($mask) {
576
        if(class_exists($mask)) {
577
            $this->default_message_mask = $mask;
578
579
            return $this;
580
        }
581
582
        throw new MaskNotFoundException("Unknown mask provided: ".$mask);
583
    }
584
585
    /**
586
     * Get the default attachment mask
587
     *
588
     * @return string
589
     */
590
    public function getDefaultAttachmentMask(){
591
        return $this->default_attachment_mask;
592
    }
593
594
    /**
595
     * Set the default attachment mask
596
     * @param $mask
597
     *
598
     * @return $this
599
     * @throws MaskNotFoundException
600
     */
601
    public function setDefaultAttachmentMask($mask) {
602
        if(class_exists($mask)) {
603
            $this->default_attachment_mask = $mask;
604
605
            return $this;
606
        }
607
608
        throw new MaskNotFoundException("Unknown mask provided: ".$mask);
609
    }
610
}
611