Completed
Push — master ( 2c2bb8...3cab00 )
by Malte
02:14
created

Message::make()   B

Complexity

Conditions 6
Paths 16

Size

Total Lines 43
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 29
c 0
b 0
f 0
dl 0
loc 43
rs 8.8337
cc 6
nc 16
nop 8

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/*
3
* File:     Message.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\InvalidMessageDateException;
17
use Webklex\PHPIMAP\Exceptions\MaskNotFoundException;
18
use Webklex\PHPIMAP\Exceptions\MessageContentFetchingException;
19
use Webklex\PHPIMAP\Exceptions\MessageHeaderFetchingException;
20
use Webklex\PHPIMAP\Exceptions\MethodNotFoundException;
21
use Webklex\PHPIMAP\Support\AttachmentCollection;
22
use Webklex\PHPIMAP\Support\FlagCollection;
23
use Webklex\PHPIMAP\Support\Masks\MessageMask;
24
use Illuminate\Support\Str;
25
use Webklex\PHPIMAP\Support\MessageCollection;
26
use Webklex\PHPIMAP\Traits\HasEvents;
27
28
/**
29
 * Class Message
30
 *
31
 * @package Webklex\PHPIMAP
32
 *
33
 * @property integer msglist
34
 * @property integer uid
35
 * @property integer msgn
36
 * @property string subject
37
 * @property string message_id
38
 * @property string message_no
39
 * @property string references
40
 * @property carbon date
41
 * @property array from
42
 * @property array to
43
 * @property array cc
44
 * @property array bcc
45
 * @property array reply_to
46
 * @property array in_reply_to
47
 * @property array sender
48
 *
49
 * @method integer getMsglist()
50
 * @method integer setMsglist(integer $msglist)
51
 * @method integer getUid()
52
 * @method integer getMsgn()
53
 * @method integer getPriority()
54
 * @method integer setPriority(integer $priority)
55
 * @method string getSubject()
56
 * @method string setSubject(string $subject)
57
 * @method string getMessageId()
58
 * @method string setMessageId(string $message_id)
59
 * @method string getMessageNo()
60
 * @method string setMessageNo(string $message_no)
61
 * @method string getReferences()
62
 * @method string setReferences(string $references)
63
 * @method carbon getDate()
64
 * @method carbon setDate(carbon $date)
65
 * @method array getFrom()
66
 * @method array setFrom(array $from)
67
 * @method array getTo()
68
 * @method array setTo(array $to)
69
 * @method array getCc()
70
 * @method array setCc(array $cc)
71
 * @method array getBcc()
72
 * @method array setBcc(array $bcc)
73
 * @method array getReplyTo()
74
 * @method array setReplyTo(array $reply_to)
75
 * @method array getInReplyTo()
76
 * @method array setInReplyTo(array $in_reply_to)
77
 * @method array getSender()
78
 * @method array setSender(array $sender)
79
 */
80
class Message {
81
    use HasEvents;
82
83
    /**
84
     * Client instance
85
     *
86
     * @var Client
87
     */
88
    private $client = Client::class;
89
90
    /**
91
     * Default mask
92
     *
93
     * @var string $mask
94
     */
95
    protected $mask = MessageMask::class;
96
97
    /**
98
     * Used config
99
     *
100
     * @var array $config
101
     */
102
    protected $config = [];
103
104
    /**
105
     * Attribute holder
106
     *
107
     * @var array $attributes
108
     */
109
    protected $attributes = [
110
        'message_no' => null,
111
    ];
112
113
    /**
114
     * The message folder path
115
     *
116
     * @var string $folder_path
117
     */
118
    protected $folder_path;
119
120
    /**
121
     * Fetch body options
122
     *
123
     * @var integer
124
     */
125
    public $fetch_options = null;
126
127
    /**
128
     * @var integer
129
     */
130
    protected $sequence = IMAP::NIL;
131
132
    /**
133
     * Fetch body options
134
     *
135
     * @var bool
136
     */
137
    public $fetch_body = null;
138
139
    /**
140
     * Fetch flags options
141
     *
142
     * @var bool
143
     */
144
    public $fetch_flags = null;
145
146
    /**
147
     * @var Header $header
148
     */
149
    public $header = null;
150
151
    /**
152
     * Raw message body
153
     *
154
     * @var null|string $raw_body
155
     */
156
    public $raw_body = null;
157
158
    /**
159
     * Message structure
160
     *
161
     * @var Structure $structure
162
     */
163
    protected $structure = null;
164
165
    /**
166
     * Message body components
167
     *
168
     * @var array   $bodies
169
     * @var AttachmentCollection|array $attachments
170
     * @var FlagCollection|array       $flags
171
     */
172
    public $bodies = [];
173
    public $attachments = [];
174
    public $flags = [];
175
176
    /**
177
     * A list of all available and supported flags
178
     *
179
     * @var array $available_flags
180
     */
181
    private $available_flags = null;
182
183
    /**
184
     * Message constructor.
185
     * @param integer $uid
186
     * @param integer|null $msglist
187
     * @param Client $client
188
     * @param integer|null $fetch_options
189
     * @param boolean $fetch_body
190
     * @param boolean $fetch_flags
191
     * @param integer $sequence
192
     *
193
     * @throws Exceptions\ConnectionFailedException
194
     * @throws InvalidMessageDateException
195
     * @throws Exceptions\RuntimeException
196
     * @throws MessageHeaderFetchingException
197
     * @throws MessageContentFetchingException
198
     * @throws Exceptions\EventNotFoundException
199
     */
200
    public function __construct($uid, $msglist, Client $client, $fetch_options = null, $fetch_body = false, $fetch_flags = false, $sequence = null) {
201
202
        $default_mask = $client->getDefaultMessageMask();
203
        if($default_mask != null) {
204
            $this->mask = $default_mask;
205
        }
206
        $this->events["message"] = $client->getDefaultEvents("message");
207
        $this->events["flag"] = $client->getDefaultEvents("flag");
208
209
        $this->folder_path = $client->getFolderPath();
0 ignored issues
show
Documentation Bug introduced by
It seems like $client->getFolderPath() of type Webklex\PHPIMAP\Folder is incompatible with the declared type string of property $folder_path.

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...
210
211
        $this->config = ClientManager::get('options');
212
        $this->available_flags = ClientManager::get('flags');
213
214
        $this->setSequence($sequence);
215
        $this->setFetchOption($fetch_options);
216
        $this->setFetchBodyOption($fetch_body);
217
        $this->setFetchFlagsOption($fetch_flags);
218
219
        $this->attachments = AttachmentCollection::make([]);
220
        $this->flags = FlagCollection::make([]);
221
222
        $this->client = $client;
223
        $this->client->openFolder($this->folder_path);
0 ignored issues
show
Bug introduced by
$this->folder_path of type Webklex\PHPIMAP\Folder is incompatible with the type string expected by parameter $folder of Webklex\PHPIMAP\Client::openFolder(). ( Ignorable by Annotation )

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

223
        $this->client->openFolder(/** @scrutinizer ignore-type */ $this->folder_path);
Loading history...
224
225
        if ($this->sequence === IMAP::ST_UID) {
226
            $this->uid = $uid;
227
            $this->msgn = $this->client->getConnection()->getMessageNumber($this->uid);
0 ignored issues
show
Bug introduced by
The method getMessageNumber() 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

227
            $this->msgn = $this->client->getConnection()->/** @scrutinizer ignore-call */ getMessageNumber($this->uid);
Loading history...
228
        }else{
229
            $this->msgn = $uid;
230
            $this->uid = $this->client->getConnection()->getUid($this->msgn);
0 ignored issues
show
Bug introduced by
The method getUid() 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

230
            $this->uid = $this->client->getConnection()->/** @scrutinizer ignore-call */ getUid($this->msgn);
Loading history...
231
        }
232
        $this->msglist = $msglist;
233
234
        if ($this->fetch_options == IMAP::FT_PEEK) {
235
            $this->parseFlags();
236
        }
237
238
        $this->parseHeader();
239
240
        if ($this->getFetchBodyOption() === true) {
241
            $this->parseBody();
242
        }
243
244
        if ($this->getFetchFlagsOption() === true && $this->fetch_options !== IMAP::FT_PEEK) {
245
            $this->parseFlags();
246
        }
247
    }
248
249
    /**
250
     * Create a new instance without fetching the message header and providing them raw instead
251
     * @param int $uid
252
     * @param int|null $msglist
253
     * @param Client $client
254
     * @param string $raw_header
255
     * @param string $raw_body
256
     * @param string $raw_flags
257
     * @param null $fetch_options
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $fetch_options is correct as it would always require null to be passed?
Loading history...
258
     * @param null $sequence
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $sequence is correct as it would always require null to be passed?
Loading history...
259
     *
260
     * @return Message
261
     * @throws Exceptions\ConnectionFailedException
262
     * @throws Exceptions\EventNotFoundException
263
     * @throws Exceptions\RuntimeException
264
     * @throws InvalidMessageDateException
265
     * @throws MessageContentFetchingException
266
     * @throws \ReflectionException
267
     */
268
    public static function make($uid, $msglist, Client $client, $raw_header, $raw_body, $raw_flags, $fetch_options = null, $sequence = null){
269
        $reflection = new \ReflectionClass(self::class);
270
        /** @var self $instance */
271
        $instance = $reflection->newInstanceWithoutConstructor();
272
273
        $default_mask = $client->getDefaultMessageMask();
274
        if($default_mask != null) {
275
            $instance->setMask($default_mask);
276
        }
277
        $instance->setEvents([
278
            "message" => $client->getDefaultEvents("message"),
279
            "flag" => $client->getDefaultEvents("flag"),
280
        ]);
281
        $instance->setFolderPath($client->getFolderPath());
282
        $instance->setConfig(ClientManager::get('options'));
283
        $instance->setAvailableFlags(ClientManager::get('flags'));
284
        $instance->setSequence($sequence);
285
        $instance->setFetchOption($fetch_options);
286
287
        $instance->setAttachments(AttachmentCollection::make([]));
288
289
        $instance->setClient($client);
290
291
        if ($instance->getSequence() === IMAP::ST_UID) {
292
            $instance->setUid($uid);
293
            $instance->setMsglist($msglist);
294
        }else{
295
            $instance->setMsgn($uid, $msglist);
0 ignored issues
show
Bug introduced by
It seems like $msglist can also be of type integer; however, parameter $msglist of Webklex\PHPIMAP\Message::setMsgn() does only seem to accept null, 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

295
            $instance->setMsgn($uid, /** @scrutinizer ignore-type */ $msglist);
Loading history...
296
        }
297
298
        $instance->parseRawHeader($raw_header);
299
        $instance->parseRawFlags($raw_flags);
300
        $instance->parseRawBody($raw_body);
301
302
        if ($fetch_options == IMAP::FT_PEEK) {
303
            if ($instance->getFlags()->get("seen") == null) {
304
                $instance->unsetFlag("Seen");
305
            }
306
        } elseif ($instance->getFlags()->get("seen") == null) {
307
            $instance->setFlag("Seen");
308
        }
309
310
        return $instance;
311
    }
312
313
    /**
314
     * Call dynamic attribute setter and getter methods
315
     * @param string $method
316
     * @param array $arguments
317
     *
318
     * @return mixed
319
     * @throws MethodNotFoundException
320
     */
321
    public function __call($method, $arguments) {
322
        if(strtolower(substr($method, 0, 3)) === 'get') {
323
            $name = Str::snake(substr($method, 3));
324
            return $this->get($name);
325
        }elseif (strtolower(substr($method, 0, 3)) === 'set') {
326
            $name = Str::snake(substr($method, 3));
327
328
            if(in_array($name, array_keys($this->attributes))) {
329
                $this->attributes[$name] = array_pop($arguments);
330
331
                return $this->attributes[$name];
332
            }
333
334
        }
335
336
        throw new MethodNotFoundException("Method ".self::class.'::'.$method.'() is not supported');
337
    }
338
339
    /**
340
     * Magic setter
341
     * @param $name
342
     * @param $value
343
     *
344
     * @return mixed
345
     */
346
    public function __set($name, $value) {
347
        $this->attributes[$name] = $value;
348
349
        return $this->attributes[$name];
350
    }
351
352
    /**
353
     * Magic getter
354
     * @param $name
355
     *
356
     * @return mixed|null
357
     */
358
    public function __get($name) {
359
        return $this->get($name);
360
    }
361
362
    /**
363
     * Get an available message or message header attribute
364
     * @param $name
365
     *
366
     * @return mixed|null
367
     */
368
    public function get($name) {
369
        if(isset($this->attributes[$name])) {
370
            return $this->attributes[$name];
371
        }
372
373
        return $this->header->get($name);
374
    }
375
376
    /**
377
     * Check if the Message has a text body
378
     *
379
     * @return bool
380
     */
381
    public function hasTextBody() {
382
        return isset($this->bodies['text']);
383
    }
384
385
    /**
386
     * Get the Message text body
387
     *
388
     * @return mixed
389
     */
390
    public function getTextBody() {
391
        if (!isset($this->bodies['text'])) {
392
            return null;
393
        }
394
395
        return $this->bodies['text'];
396
    }
397
398
    /**
399
     * Check if the Message has a html body
400
     *
401
     * @return bool
402
     */
403
    public function hasHTMLBody() {
404
        return isset($this->bodies['html']);
405
    }
406
407
    /**
408
     * Get the Message html body
409
     *
410
     * @return string|null
411
     */
412
    public function getHTMLBody() {
413
        if (!isset($this->bodies['html'])) {
414
            return null;
415
        }
416
417
        return $this->bodies['html'];
418
    }
419
420
    /**
421
     * Parse all defined headers
422
     *
423
     * @throws Exceptions\ConnectionFailedException
424
     * @throws Exceptions\RuntimeException
425
     * @throws InvalidMessageDateException
426
     * @throws MessageHeaderFetchingException
427
     */
428
    private function parseHeader() {
429
        $sequence_id = $this->getSequenceId();
430
        $headers = $this->client->getConnection()->headers([$sequence_id], "RFC822", $this->sequence === IMAP::ST_UID);
0 ignored issues
show
Bug introduced by
The method headers() 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

430
        $headers = $this->client->getConnection()->/** @scrutinizer ignore-call */ headers([$sequence_id], "RFC822", $this->sequence === IMAP::ST_UID);
Loading history...
431
        if (!isset($headers[$sequence_id])) {
432
            throw new MessageHeaderFetchingException("no headers found", 0);
433
        }
434
435
        $this->parseRawHeader($headers[$sequence_id]);
436
    }
437
438
    /**
439
     * @param string $raw_header
440
     *
441
     * @throws InvalidMessageDateException
442
     */
443
    public function parseRawHeader($raw_header){
444
        $this->header = new Header($raw_header);
445
    }
446
447
    /**
448
     * Parse additional raw flags
449
     * @param $raw_flags
450
     */
451
    public function parseRawFlags($raw_flags) {
452
        $this->flags = FlagCollection::make([]);
453
454
        foreach($raw_flags as $flag) {
455
            if (strpos($flag, "\\") === 0){
456
                $flag = substr($flag, 1);
457
            }
458
            $flag_key = strtolower($flag);
459
            if (in_array($flag_key, $this->available_flags) || $this->available_flags === null) {
460
                $this->flags->put($flag_key, $flag);
461
            }
462
        }
463
    }
464
465
    /**
466
     * Parse additional flags
467
     *
468
     * @return void
469
     * @throws Exceptions\ConnectionFailedException
470
     * @throws Exceptions\RuntimeException
471
     */
472
    private function parseFlags() {
473
        $this->client->openFolder($this->folder_path);
474
        $this->flags = FlagCollection::make([]);
475
476
        $sequence_id = $this->getSequenceId();
477
        $flags = $this->client->getConnection()->flags([$sequence_id], $this->sequence === IMAP::ST_UID);
0 ignored issues
show
Bug introduced by
The method flags() 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

477
        $flags = $this->client->getConnection()->/** @scrutinizer ignore-call */ flags([$sequence_id], $this->sequence === IMAP::ST_UID);
Loading history...
478
479
        if (isset($flags[$sequence_id])) {
480
            $this->parseRawFlags($flags[$sequence_id]);
481
        }
482
    }
483
484
    /**
485
     * Parse the Message body
486
     *
487
     * @return $this
488
     * @throws Exceptions\ConnectionFailedException
489
     * @throws Exceptions\MessageContentFetchingException
490
     * @throws InvalidMessageDateException
491
     * @throws Exceptions\RuntimeException
492
     * @throws Exceptions\EventNotFoundException
493
     */
494
    public function parseBody() {
495
        $this->client->openFolder($this->folder_path);
496
497
        $sequence_id = $this->getSequenceId();
498
        $contents = $this->client->getConnection()->content([$sequence_id], "RFC822", $this->sequence === IMAP::ST_UID);
0 ignored issues
show
Bug introduced by
The method content() 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

498
        $contents = $this->client->getConnection()->/** @scrutinizer ignore-call */ content([$sequence_id], "RFC822", $this->sequence === IMAP::ST_UID);
Loading history...
499
        if (!isset($contents[$sequence_id])) {
500
            throw new MessageContentFetchingException("no content found", 0);
501
        }
502
        $content = $contents[$sequence_id];
503
504
        $body = $this->parseRawBody($content);
505
506
        if ($this->fetch_options == IMAP::FT_PEEK) {
507
            if ($this->getFlags()->get("seen") == null) {
508
                $this->unsetFlag("Seen");
509
            }
510
        }elseif ($this->getFlags()->get("seen") != null) {
511
            $this->setFlag("Seen");
512
        }
513
514
        return $body;
515
    }
516
517
    /**
518
     * Parse a given message body
519
     * @param string $raw_body
520
     *
521
     * @return $this
522
     * @throws Exceptions\ConnectionFailedException
523
     * @throws Exceptions\EventNotFoundException
524
     * @throws Exceptions\RuntimeException
525
     * @throws InvalidMessageDateException
526
     * @throws MessageContentFetchingException
527
     */
528
    public function parseRawBody($raw_body) {
529
        $this->structure = new Structure($raw_body, $this->header);
530
531
        $this->fetchStructure($this->structure);
532
533
        return $this;
534
    }
535
536
    /**
537
     * Fetch the Message structure
538
     * @param $structure
539
     *
540
     * @throws Exceptions\ConnectionFailedException
541
     */
542
    private function fetchStructure($structure) {
543
        $this->client->openFolder($this->folder_path);
544
545
        foreach ($structure->parts as $part) {
546
            $this->fetchPart($part);
547
        }
548
    }
549
550
    /**
551
     * Fetch a given part
552
     * @param Part $part
553
     */
554
    private function fetchPart(Part $part) {
555
556
        if ($part->isAttachment()) {
557
            $this->fetchAttachment($part);
558
        }else{
559
            $encoding = $this->getEncoding($part);
560
561
            $content = $this->decodeString($part->content, $part->encoding);
562
563
            // We don't need to do convertEncoding() if charset is ASCII (us-ascii):
564
            //     ASCII is a subset of UTF-8, so all ASCII files are already UTF-8 encoded
565
            //     https://stackoverflow.com/a/11303410
566
            //
567
            // us-ascii is the same as ASCII:
568
            //     ASCII is the traditional name for the encoding system; the Internet Assigned Numbers Authority (IANA)
569
            //     prefers the updated name US-ASCII, which clarifies that this system was developed in the US and
570
            //     based on the typographical symbols predominantly in use there.
571
            //     https://en.wikipedia.org/wiki/ASCII
572
            //
573
            // convertEncoding() function basically means convertToUtf8(), so when we convert ASCII string into UTF-8 it gets broken.
574
            if ($encoding != 'us-ascii') {
575
                $content = $this->convertEncoding($content, $encoding);
576
            }
577
578
            $subtype = strtolower($part->subtype);
579
            $subtype = $subtype == "plain" || $subtype == "" ? "text" : $subtype;
580
581
            $this->bodies[$subtype] = $content;
582
        }
583
    }
584
585
    /**
586
     * Fetch the Message attachment
587
     * @param Part $part
588
     */
589
    protected function fetchAttachment($part) {
590
591
        $oAttachment = new Attachment($this, $part);
592
593
        if ($oAttachment->getName() !== null && $oAttachment->getSize() > 0) {
594
            if ($oAttachment->getId() !== null) {
0 ignored issues
show
introduced by
The condition $oAttachment->getId() !== null is always true.
Loading history...
595
                $this->attachments->put($oAttachment->getId(), $oAttachment);
596
            } else {
597
                $this->attachments->push($oAttachment);
598
            }
599
        }
600
    }
601
602
    /**
603
     * Fail proof setter for $fetch_option
604
     * @param $option
605
     *
606
     * @return $this
607
     */
608
    public function setFetchOption($option) {
609
        if (is_long($option) === true) {
610
            $this->fetch_options = $option;
611
        } elseif (is_null($option) === true) {
612
            $config = ClientManager::get('options.fetch', IMAP::FT_UID);
613
            $this->fetch_options = is_long($config) ? $config : 1;
614
        }
615
616
        return $this;
617
    }
618
619
    /**
620
     * Set the sequence type
621
     * @param int $sequence
622
     *
623
     * @return $this
624
     */
625
    public function setSequence($sequence) {
626
        if (is_long($sequence)) {
0 ignored issues
show
introduced by
The condition is_long($sequence) is always true.
Loading history...
627
            $this->sequence = $sequence;
628
        } elseif (is_null($sequence)) {
629
            $config = ClientManager::get('options.sequence', IMAP::ST_MSGN);
630
            $this->sequence = is_long($config) ? $config : IMAP::ST_MSGN;
631
        }
632
633
        return $this;
634
    }
635
636
    /**
637
     * Fail proof setter for $fetch_body
638
     * @param $option
639
     *
640
     * @return $this
641
     */
642
    public function setFetchBodyOption($option) {
643
        if (is_bool($option)) {
644
            $this->fetch_body = $option;
645
        } elseif (is_null($option)) {
646
            $config = ClientManager::get('options.fetch_body', true);
647
            $this->fetch_body = is_bool($config) ? $config : true;
648
        }
649
650
        return $this;
651
    }
652
653
    /**
654
     * Fail proof setter for $fetch_flags
655
     * @param $option
656
     *
657
     * @return $this
658
     */
659
    public function setFetchFlagsOption($option) {
660
        if (is_bool($option)) {
661
            $this->fetch_flags = $option;
662
        } elseif (is_null($option)) {
663
            $config = ClientManager::get('options.fetch_flags', true);
664
            $this->fetch_flags = is_bool($config) ? $config : true;
665
        }
666
667
        return $this;
668
    }
669
670
    /**
671
     * Decode a given string
672
     * @param $string
673
     * @param $encoding
674
     *
675
     * @return string
676
     */
677
    public function decodeString($string, $encoding) {
678
        switch ($encoding) {
679
            case IMAP::MESSAGE_ENC_BINARY:
680
                if (extension_loaded('imap')) {
681
                    return base64_decode(\imap_binary($string));
682
                }
683
                return base64_decode($string);
684
            case IMAP::MESSAGE_ENC_BASE64:
685
                return base64_decode($string);
686
            case IMAP::MESSAGE_ENC_8BIT:
687
            case IMAP::MESSAGE_ENC_QUOTED_PRINTABLE:
688
                return quoted_printable_decode($string);
689
            case IMAP::MESSAGE_ENC_7BIT:
690
            case IMAP::MESSAGE_ENC_OTHER:
691
            default:
692
                return $string;
693
        }
694
    }
695
696
    /**
697
     * Convert the encoding
698
     * @param $str
699
     * @param string $from
700
     * @param string $to
701
     *
702
     * @return mixed|string
703
     */
704
    public function convertEncoding($str, $from = "ISO-8859-2", $to = "UTF-8") {
705
706
        $from = EncodingAliases::get($from);
707
        $to = EncodingAliases::get($to);
708
709
        if ($from === $to) {
710
            return $str;
711
        }
712
713
        // We don't need to do convertEncoding() if charset is ASCII (us-ascii):
714
        //     ASCII is a subset of UTF-8, so all ASCII files are already UTF-8 encoded
715
        //     https://stackoverflow.com/a/11303410
716
        //
717
        // us-ascii is the same as ASCII:
718
        //     ASCII is the traditional name for the encoding system; the Internet Assigned Numbers Authority (IANA)
719
        //     prefers the updated name US-ASCII, which clarifies that this system was developed in the US and
720
        //     based on the typographical symbols predominantly in use there.
721
        //     https://en.wikipedia.org/wiki/ASCII
722
        //
723
        // convertEncoding() function basically means convertToUtf8(), so when we convert ASCII string into UTF-8 it gets broken.
724
        if (strtolower($from) == 'us-ascii' && $to == 'UTF-8') {
725
            return $str;
726
        }
727
728
        if (function_exists('iconv') && $from != 'UTF-7' && $to != 'UTF-7') {
729
            return @iconv($from, $to.'//IGNORE', $str);
730
        } else {
731
            if (!$from) {
732
                return mb_convert_encoding($str, $to);
733
            }
734
            return mb_convert_encoding($str, $to, $from);
735
        }
736
    }
737
738
    /**
739
     * Get the encoding of a given abject
740
     * @param object|string $structure
741
     *
742
     * @return string
743
     */
744
    public function getEncoding($structure) {
745
        if (property_exists($structure, 'parameters')) {
746
            foreach ($structure->parameters as $parameter) {
747
                if (strtolower($parameter->attribute) == "charset") {
748
                    return EncodingAliases::get($parameter->value);
749
                }
750
            }
751
        }elseif (property_exists($structure, 'charset')){
752
            return EncodingAliases::get($structure->charset);
753
        }elseif (is_string($structure) === true){
754
            return mb_detect_encoding($structure);
755
        }
756
757
        return 'UTF-8';
758
    }
759
760
    /**
761
     * Get the messages folder
762
     *
763
     * @return mixed
764
     * @throws Exceptions\ConnectionFailedException
765
     * @throws Exceptions\FolderFetchingException
766
     */
767
    public function getFolder(){
768
        return $this->client->getFolder($this->folder_path);
769
    }
770
771
    /**
772
     * Create a message thread based on the current message
773
     * @param Folder|null $sent_folder
774
     * @param MessageCollection|null $thread
775
     * @param Folder|null $folder
776
     *
777
     * @return MessageCollection|null
778
     * @throws Exceptions\ConnectionFailedException
779
     * @throws Exceptions\FolderFetchingException
780
     * @throws Exceptions\GetMessagesFailedException
781
     */
782
    public function thread($sent_folder = null, &$thread = null, $folder = null){
783
        $thread = $thread ? $thread : MessageCollection::make([]);
784
        $folder = $folder ? $folder :  $this->getFolder();
785
        $sent_folder = $sent_folder ? $sent_folder : $this->client->getFolder(ClientManager::get("options.common_folders.sent", "INBOX/Sent"));
786
787
        /** @var Message $message */
788
        foreach($thread as $message) {
789
            if ($message->message_id == $this->message_id) {
790
                return $thread;
791
            }
792
        }
793
        $thread->push($this);
794
795
        $folder->query()->inReplyTo($this->message_id)
796
            ->setFetchBody($this->getFetchBodyOption())
797
            ->leaveUnread()->get()->each(function($message) use(&$thread, $folder, $sent_folder){
798
            /** @var Message $message */
799
            $message->thread($sent_folder, $thread, $folder);
800
        });
801
        $sent_folder->query()->inReplyTo($this->message_id)
802
            ->setFetchBody($this->getFetchBodyOption())
803
            ->leaveUnread()->get()->each(function($message) use(&$thread, $folder, $sent_folder){
804
            /** @var Message $message */
805
                $message->thread($sent_folder, $thread, $folder);
806
        });
807
808
        if (is_array($this->in_reply_to)) {
0 ignored issues
show
introduced by
The condition is_array($this->in_reply_to) is always true.
Loading history...
809
            foreach($this->in_reply_to as $in_reply_to) {
810
                $folder->query()->messageId($in_reply_to)
811
                    ->setFetchBody($this->getFetchBodyOption())
812
                    ->leaveUnread()->get()->each(function($message) use(&$thread, $folder, $sent_folder){
813
                    /** @var Message $message */
814
                        $message->thread($sent_folder, $thread, $folder);
815
                });
816
                $sent_folder->query()->messageId($in_reply_to)
817
                    ->setFetchBody($this->getFetchBodyOption())
818
                    ->leaveUnread()->get()->each(function($message) use(&$thread, $folder, $sent_folder){
819
                    /** @var Message $message */
820
                        $message->thread($sent_folder, $thread, $folder);
821
                });
822
            }
823
        }
824
825
        return $thread;
826
    }
827
828
    /**
829
     * Copy the current Messages to a mailbox
830
     * @param string $folder_path
831
     * @param boolean $expunge
832
     *
833
     * @return null|Message
834
     * @throws Exceptions\ConnectionFailedException
835
     * @throws Exceptions\FolderFetchingException
836
     * @throws Exceptions\RuntimeException
837
     * @throws InvalidMessageDateException
838
     * @throws MessageContentFetchingException
839
     * @throws MessageHeaderFetchingException
840
     * @throws Exceptions\EventNotFoundException
841
     */
842
    public function copy($folder_path, $expunge = false) {
843
        $this->client->openFolder($folder_path);
844
        $status = $this->client->getConnection()->examineFolder($folder_path);
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

844
        $status = $this->client->getConnection()->/** @scrutinizer ignore-call */ examineFolder($folder_path);
Loading history...
845
846
        if (isset($status["uidnext"])) {
847
            $next_uid = $status["uidnext"];
848
849
            /** @var Folder $folder */
850
            $folder = $this->client->getFolder($folder_path);
851
852
            $this->client->openFolder($this->folder_path);
853
            if ($this->client->getConnection()->copyMessage($folder->path, $this->getSequenceId(), null, $this->sequence === IMAP::ST_UID) == true) {
0 ignored issues
show
Bug introduced by
The method copyMessage() 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

853
            if ($this->client->getConnection()->/** @scrutinizer ignore-call */ copyMessage($folder->path, $this->getSequenceId(), null, $this->sequence === IMAP::ST_UID) == true) {
Loading history...
854
                if($expunge) $this->client->expunge();
855
856
                $this->client->openFolder($folder->path);
857
858
                if ($this->sequence === IMAP::ST_UID) {
859
                    $sequence_id = $next_uid;
860
                }else{
861
                    $sequence_id = $this->client->getConnection()->getMessageNumber($next_uid);
862
                }
863
864
                $message = $folder->query()->getMessage($sequence_id, null, $this->sequence);
865
                $event = $this->getEvent("message", "copied");
866
                $event::dispatch($this, $message);
867
868
                return $message;
869
            }
870
        }
871
872
        return null;
873
    }
874
875
    /**
876
     * Move the current Messages to a mailbox
877
     * @param string $folder_path
878
     * @param boolean $expunge
879
     *
880
     * @return Message|null
881
     * @throws Exceptions\ConnectionFailedException
882
     * @throws Exceptions\FolderFetchingException
883
     * @throws Exceptions\RuntimeException
884
     * @throws InvalidMessageDateException
885
     * @throws MessageContentFetchingException
886
     * @throws MessageHeaderFetchingException
887
     * @throws Exceptions\EventNotFoundException
888
     */
889
    public function move($folder_path, $expunge = false) {
890
        $this->client->openFolder($folder_path);
891
        $status = $this->client->getConnection()->examineFolder($folder_path);
892
893
        if (isset($status["uidnext"])) {
894
            $next_uid = $status["uidnext"];
895
896
            /** @var Folder $folder */
897
            $folder = $this->client->getFolder($folder_path);
898
899
            $this->client->openFolder($this->folder_path);
900
            if ($this->client->getConnection()->moveMessage($folder->path, $this->getSequenceId(), null, $this->sequence === IMAP::ST_UID) == true) {
0 ignored issues
show
Bug introduced by
The method moveMessage() 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

900
            if ($this->client->getConnection()->/** @scrutinizer ignore-call */ moveMessage($folder->path, $this->getSequenceId(), null, $this->sequence === IMAP::ST_UID) == true) {
Loading history...
901
                if($expunge) $this->client->expunge();
902
903
                $this->client->openFolder($folder->path);
904
905
                if ($this->sequence === IMAP::ST_UID) {
906
                    $sequence_id = $next_uid;
907
                }else{
908
                    $sequence_id = $this->client->getConnection()->getMessageNumber($next_uid);
909
                }
910
911
                $message = $folder->query()->getMessage($sequence_id, null, $this->sequence);
912
                $event = $this->getEvent("message", "moved");
913
                $event::dispatch($this, $message);
914
915
                return $message;
916
            }
917
        }
918
919
        return null;
920
    }
921
922
    /**
923
     * Delete the current Message
924
     * @param bool $expunge
925
     *
926
     * @return bool
927
     * @throws Exceptions\ConnectionFailedException
928
     * @throws Exceptions\RuntimeException
929
     * @throws Exceptions\EventNotFoundException
930
     */
931
    public function delete($expunge = true) {
932
        $status = $this->setFlag("Deleted");
933
        if($expunge) $this->client->expunge();
934
935
        $event = $this->getEvent("message", "deleted");
936
        $event::dispatch($this);
937
938
        return $status;
939
    }
940
941
    /**
942
     * Restore a deleted Message
943
     * @param boolean $expunge
944
     *
945
     * @return bool
946
     * @throws Exceptions\ConnectionFailedException
947
     * @throws Exceptions\RuntimeException
948
     * @throws Exceptions\EventNotFoundException
949
     */
950
    public function restore($expunge = true) {
951
        $status = $this->unsetFlag("Deleted");
952
        if($expunge) $this->client->expunge();
953
954
        $event = $this->getEvent("message", "restored");
955
        $event::dispatch($this);
956
957
        return $status;
958
    }
959
960
    /**
961
     * Set a given flag
962
     * @param string|array $flag
963
     *
964
     * @return bool
965
     * @throws Exceptions\ConnectionFailedException
966
     * @throws Exceptions\RuntimeException
967
     * @throws Exceptions\EventNotFoundException
968
     */
969
    public function setFlag($flag) {
970
        $this->client->openFolder($this->folder_path);
971
        $flag = "\\".trim(is_array($flag) ? implode(" \\", $flag) : $flag);
972
        $sequence_id = $this->getSequenceId();
973
        $status = $this->client->getConnection()->store([$flag], $sequence_id, $sequence_id, "+", true, $this->sequence === IMAP::ST_UID);
0 ignored issues
show
Bug introduced by
The method store() 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

973
        $status = $this->client->getConnection()->/** @scrutinizer ignore-call */ store([$flag], $sequence_id, $sequence_id, "+", true, $this->sequence === IMAP::ST_UID);
Loading history...
974
        $this->parseFlags();
975
976
        $event = $this->getEvent("flag", "new");
977
        $event::dispatch($this, $flag);
978
979
        return $status;
980
    }
981
982
    /**
983
     * Unset a given flag
984
     * @param string|array $flag
985
     *
986
     * @return bool
987
     * @throws Exceptions\ConnectionFailedException
988
     * @throws Exceptions\RuntimeException
989
     * @throws Exceptions\EventNotFoundException
990
     */
991
    public function unsetFlag($flag) {
992
        $this->client->openFolder($this->folder_path);
993
994
        $flag = "\\".trim(is_array($flag) ? implode(" \\", $flag) : $flag);
995
        $sequence_id = $this->getSequenceId();
996
        $status = $this->client->getConnection()->store([$flag], $sequence_id, $sequence_id, "-", true, $this->sequence === IMAP::ST_UID);
997
        $this->parseFlags();
998
999
        $event = $this->getEvent("flag", "deleted");
1000
        $event::dispatch($this, $flag);
1001
1002
        return $status;
1003
    }
1004
1005
    /**
1006
     * Get all message attachments.
1007
     *
1008
     * @return AttachmentCollection
1009
     */
1010
    public function getAttachments() {
1011
        return $this->attachments;
1012
    }
1013
1014
    /**
1015
     * Checks if there are any attachments present
1016
     *
1017
     * @return boolean
1018
     */
1019
    public function hasAttachments() {
1020
        return $this->attachments->isEmpty() === false;
1021
    }
1022
1023
    /**
1024
     * Get the raw body
1025
     *
1026
     * @return string
1027
     * @throws Exceptions\ConnectionFailedException
1028
     */
1029
    public function getRawBody() {
1030
        if ($this->raw_body === null) {
1031
            $this->client->openFolder($this->folder_path);
1032
1033
            $this->raw_body = $this->structure->raw;
1034
        }
1035
1036
        return $this->raw_body;
1037
    }
1038
1039
    /**
1040
     * Get the message header
1041
     *
1042
     * @return Header
1043
     */
1044
    public function getHeader() {
1045
        return $this->header;
1046
    }
1047
1048
    /**
1049
     * Get the current client
1050
     *
1051
     * @return Client
1052
     */
1053
    public function getClient() {
1054
        return $this->client;
1055
    }
1056
1057
    /**
1058
     * Get the used fetch option
1059
     *
1060
     * @return integer
1061
     */
1062
    public function getFetchOptions() {
1063
        return $this->fetch_options;
1064
    }
1065
1066
    /**
1067
     * Get the used fetch body option
1068
     *
1069
     * @return boolean
1070
     */
1071
    public function getFetchBodyOption() {
1072
        return $this->fetch_body;
1073
    }
1074
1075
    /**
1076
     * Get the used fetch flags option
1077
     *
1078
     * @return boolean
1079
     */
1080
    public function getFetchFlagsOption() {
1081
        return $this->fetch_flags;
1082
    }
1083
1084
    /**
1085
     * Get all available bodies
1086
     *
1087
     * @return array
1088
     */
1089
    public function getBodies() {
1090
        return $this->bodies;
1091
    }
1092
1093
    /**
1094
     * Get all set flags
1095
     *
1096
     * @return FlagCollection
1097
     */
1098
    public function getFlags() {
1099
        return $this->flags;
1100
    }
1101
1102
    /**
1103
     * Get the fetched structure
1104
     *
1105
     * @return Structure|null
1106
     */
1107
    public function getStructure(){
1108
        return $this->structure;
1109
    }
1110
1111
    /**
1112
     * Check if a message matches an other by comparing basic attributes
1113
     *
1114
     * @param  null|Message $message
1115
     * @return boolean
1116
     */
1117
    public function is(Message $message = null) {
1118
        if (is_null($message)) {
1119
            return false;
1120
        }
1121
1122
        return $this->uid == $message->uid
1123
            && $this->message_id == $message->message_id
1124
            && $this->subject == $message->subject
1125
            && $this->date->eq($message->date);
1126
    }
1127
1128
    /**
1129
     * Get all message attributes
1130
     *
1131
     * @return array
1132
     */
1133
    public function getAttributes(){
1134
        return array_merge($this->attributes, $this->header->getAttributes());
1135
    }
1136
1137
    /**
1138
     * Set the message mask
1139
     * @param $mask
1140
     *
1141
     * @return $this
1142
     */
1143
    public function setMask($mask){
1144
        if(class_exists($mask)){
1145
            $this->mask = $mask;
1146
        }
1147
1148
        return $this;
1149
    }
1150
1151
    /**
1152
     * Get the used message mask
1153
     *
1154
     * @return string
1155
     */
1156
    public function getMask(){
1157
        return $this->mask;
1158
    }
1159
1160
    /**
1161
     * Get a masked instance by providing a mask name
1162
     * @param string|null $mask
1163
     *
1164
     * @return mixed
1165
     * @throws MaskNotFoundException
1166
     */
1167
    public function mask($mask = null){
1168
        $mask = $mask !== null ? $mask : $this->mask;
1169
        if(class_exists($mask)){
1170
            return new $mask($this);
1171
        }
1172
1173
        throw new MaskNotFoundException("Unknown mask provided: ".$mask);
1174
    }
1175
1176
    /**
1177
     * Set the message path aka folder path
1178
     * @param $folder_path
1179
     *
1180
     * @return $this
1181
     */
1182
    public function setFolderPath($folder_path){
1183
        $this->folder_path = $folder_path;
1184
1185
        return $this;
1186
    }
1187
1188
    /**
1189
     * Set the config
1190
     * @param $config
1191
     *
1192
     * @return $this
1193
     */
1194
    public function setConfig($config){
1195
        $this->config = $config;
1196
1197
        return $this;
1198
    }
1199
1200
    /**
1201
     * Set the available flags
1202
     * @param $available_flags
1203
     *
1204
     * @return $this
1205
     */
1206
    public function setAvailableFlags($available_flags){
1207
        $this->available_flags = $available_flags;
1208
1209
        return $this;
1210
    }
1211
1212
    /**
1213
     * Set the attachment collection
1214
     * @param $attachments
1215
     *
1216
     * @return $this
1217
     */
1218
    public function setAttachments($attachments){
1219
        $this->attachments = $attachments;
1220
1221
        return $this;
1222
    }
1223
1224
    /**
1225
     * Set the flag collection
1226
     * @param $flags
1227
     *
1228
     * @return $this
1229
     */
1230
    public function setFlags($flags){
1231
        $this->flags = $flags;
1232
1233
        return $this;
1234
    }
1235
1236
    /**
1237
     * Set the client
1238
     * @param $client
1239
     *
1240
     * @throws Exceptions\ConnectionFailedException
1241
     * @return $this
1242
     */
1243
    public function setClient($client){
1244
        $this->client = $client;
1245
        $this->client->openFolder($this->folder_path);
1246
1247
        return $this;
1248
    }
1249
1250
    /**
1251
     * Set the message number
1252
     * @param int $uid
1253
     *
1254
     * @throws Exceptions\ConnectionFailedException
1255
     * @throws Exceptions\RuntimeException
1256
     * @return $this
1257
     */
1258
    public function setUid($uid){
1259
        $this->uid = $uid;
1260
        $this->msgn = $this->client->getConnection()->getMessageNumber($this->uid);
1261
        $this->msglist = null;
1262
1263
        return $this;
1264
    }
1265
1266
    /**
1267
     * Set the message number
1268
     * @param $msgn
1269
     * @param null $msglist
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $msglist is correct as it would always require null to be passed?
Loading history...
1270
     *
1271
     * @throws Exceptions\ConnectionFailedException
1272
     * @throws Exceptions\RuntimeException
1273
     * @return $this
1274
     */
1275
    public function setMsgn($msgn, $msglist = null){
1276
        $this->msgn = $msgn;
1277
        $this->msglist = $msglist;
1278
        $this->uid = $this->client->getConnection()->getUid($this->msgn);
1279
1280
        return $this;
1281
    }
1282
1283
    /**
1284
     * Get the current sequence type
1285
     *
1286
     * @return int
1287
     */
1288
    public function getSequence(){
1289
        return $this->sequence;
1290
    }
1291
1292
    /**
1293
     * Set the sequence type
1294
     *
1295
     * @return int
1296
     */
1297
    public function getSequenceId(){
1298
        return $this->sequence === IMAP::ST_UID ? $this->uid : $this->msgn;
1299
    }
1300
}
1301