Passed
Pull Request — master (#154)
by
unknown
02:44
created

Message::make()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 27
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
eloc 19
c 2
b 0
f 1
dl 0
loc 27
rs 9.6333
cc 2
nc 2
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 ReflectionClass;
16
use ReflectionException;
17
use Webklex\PHPIMAP\Exceptions\InvalidMessageDateException;
18
use Webklex\PHPIMAP\Exceptions\MaskNotFoundException;
19
use Webklex\PHPIMAP\Exceptions\MessageContentFetchingException;
20
use Webklex\PHPIMAP\Exceptions\MessageFlagException;
21
use Webklex\PHPIMAP\Exceptions\MessageHeaderFetchingException;
22
use Webklex\PHPIMAP\Exceptions\MethodNotFoundException;
23
use Webklex\PHPIMAP\Support\AttachmentCollection;
24
use Webklex\PHPIMAP\Support\FlagCollection;
25
use Webklex\PHPIMAP\Support\Masks\MessageMask;
26
use Illuminate\Support\Str;
27
use Webklex\PHPIMAP\Support\MessageCollection;
28
use Webklex\PHPIMAP\Traits\HasEvents;
29
30
/**
31
 * Class Message
32
 *
33
 * @package Webklex\PHPIMAP
34
 *
35
 * @property integer msglist
36
 * @property integer uid
37
 * @property integer msgn
38
 * @property Attribute subject
39
 * @property Attribute message_id
40
 * @property Attribute message_no
41
 * @property Attribute references
42
 * @property Attribute date
43
 * @property Attribute from
44
 * @property Attribute to
45
 * @property Attribute cc
46
 * @property Attribute bcc
47
 * @property Attribute reply_to
48
 * @property Attribute in_reply_to
49
 * @property Attribute sender
50
 *
51
 * @method integer getMsglist()
52
 * @method integer setMsglist($msglist)
53
 * @method integer getUid()
54
 * @method integer getMsgn()
55
 * @method Attribute getPriority()
56
 * @method Attribute getSubject()
57
 * @method Attribute getMessageId()
58
 * @method Attribute getMessageNo()
59
 * @method Attribute getReferences()
60
 * @method Attribute getDate()
61
 * @method Attribute getFrom()
62
 * @method Attribute getTo()
63
 * @method Attribute getCc()
64
 * @method Attribute getBcc()
65
 * @method Attribute getReplyTo()
66
 * @method Attribute getInReplyTo()
67
 * @method Attribute getSender()
68
 */
69
class Message {
70
    use HasEvents;
71
72
    /**
73
     * Client instance
74
     *
75
     * @var Client
76
     */
77
    private $client = Client::class;
78
79
    /**
80
     * Default mask
81
     *
82
     * @var string $mask
83
     */
84
    protected $mask = MessageMask::class;
85
86
    /**
87
     * Used config
88
     *
89
     * @var array $config
90
     */
91
    protected $config = [];
92
93
    /**
94
     * Attribute holder
95
     *
96
     * @var Attribute[]|mixed[] $attributes
97
     */
98
    protected $attributes = [];
99
100
    /**
101
     * The message folder path
102
     *
103
     * @var string $folder_path
104
     */
105
    protected $folder_path;
106
107
    /**
108
     * Fetch body options
109
     *
110
     * @var integer
111
     */
112
    public $fetch_options = null;
113
114
    /**
115
     * @var integer
116
     */
117
    protected $sequence = IMAP::NIL;
118
119
    /**
120
     * Fetch body options
121
     *
122
     * @var bool
123
     */
124
    public $fetch_body = null;
125
126
    /**
127
     * Fetch flags options
128
     *
129
     * @var bool
130
     */
131
    public $fetch_flags = null;
132
133
    /**
134
     * @var Header $header
135
     */
136
    public $header = null;
137
138
    /**
139
     * Raw message body
140
     *
141
     * @var null|string $raw_body
142
     */
143
    public $raw_body = null;
144
145
    /**
146
     * Message structure
147
     *
148
     * @var Structure $structure
149
     */
150
    protected $structure = null;
151
152
    /**
153
     * Message body components
154
     *
155
     * @var array   $bodies
156
     */
157
    public $bodies = [];
158
159
    /** @var AttachmentCollection $attachments */
160
    public $attachments;
161
162
    /** @var FlagCollection $flags */
163
    public $flags;
164
165
    /**
166
     * A list of all available and supported flags
167
     *
168
     * @var array $available_flags
169
     */
170
    private $available_flags = null;
171
172
    /**
173
     * Message constructor.
174
     * @param integer $uid
175
     * @param integer|null $msglist
176
     * @param Client $client
177
     * @param integer|null $fetch_options
178
     * @param boolean $fetch_body
179
     * @param boolean $fetch_flags
180
     * @param integer $sequence
181
     *
182
     * @throws Exceptions\ConnectionFailedException
183
     * @throws InvalidMessageDateException
184
     * @throws Exceptions\RuntimeException
185
     * @throws MessageHeaderFetchingException
186
     * @throws MessageContentFetchingException
187
     * @throws Exceptions\EventNotFoundException
188
     * @throws MessageFlagException
189
     * @throws Exceptions\MessageNotFoundException
190
     */
191
    public function __construct($uid, $msglist, Client $client, $fetch_options = null, $fetch_body = false, $fetch_flags = false, $sequence = null) {
192
        $this->boot();
193
194
        $default_mask = $client->getDefaultMessageMask();
195
        if($default_mask != null) {
196
            $this->mask = $default_mask;
197
        }
198
        $this->events["message"] = $client->getDefaultEvents("message");
199
        $this->events["flag"] = $client->getDefaultEvents("flag");
200
201
        $this->folder_path = $client->getFolderPath();
202
203
        $this->setSequence($sequence);
204
        $this->setFetchOption($fetch_options);
205
        $this->setFetchBodyOption($fetch_body);
206
        $this->setFetchFlagsOption($fetch_flags);
207
208
        $this->client = $client;
209
        $this->client->openFolder($this->folder_path);
210
211
        $this->setSequenceId($uid, $msglist);
212
213
        if ($this->fetch_options == IMAP::FT_PEEK) {
214
            $this->parseFlags();
215
        }
216
217
        $this->parseHeader();
218
219
        if ($this->getFetchBodyOption() === true) {
220
            $this->parseBody();
221
        }
222
223
        if ($this->getFetchFlagsOption() === true && $this->fetch_options !== IMAP::FT_PEEK) {
224
            $this->parseFlags();
225
        }
226
    }
227
228
    /**
229
     * Create a new instance without fetching the message header and providing them raw instead
230
     * @param int $uid
231
     * @param int|null $msglist
232
     * @param Client $client
233
     * @param string $raw_header
234
     * @param string $raw_body
235
     * @param array $raw_flags
236
     * @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...
237
     * @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...
238
     *
239
     * @return Message
240
     * @throws Exceptions\ConnectionFailedException
241
     * @throws Exceptions\EventNotFoundException
242
     * @throws InvalidMessageDateException
243
     * @throws MessageContentFetchingException
244
     * @throws ReflectionException
245
     * @throws MessageFlagException
246
     * @throws Exceptions\RuntimeException
247
     * @throws Exceptions\MessageNotFoundException
248
     */
249
    public static function make($uid, $msglist, Client $client, $raw_header, $raw_body, $raw_flags, $fetch_options = null, $sequence = null){
250
        $reflection = new ReflectionClass(self::class);
251
        /** @var self $instance */
252
        $instance = $reflection->newInstanceWithoutConstructor();
253
        $instance->boot();
254
255
        $default_mask = $client->getDefaultMessageMask();
256
        if($default_mask != null) {
257
            $instance->setMask($default_mask);
258
        }
259
        $instance->setEvents([
260
            "message" => $client->getDefaultEvents("message"),
261
            "flag" => $client->getDefaultEvents("flag"),
262
        ]);
263
        $instance->setFolderPath($client->getFolderPath());
264
        $instance->setSequence($sequence);
265
        $instance->setFetchOption($fetch_options);
266
267
        $instance->setClient($client);
268
        $instance->setSequenceId($uid, $msglist);
269
270
        $instance->parseRawHeader($raw_header);
271
        $instance->parseRawFlags($raw_flags);
272
        $instance->parseRawBody($raw_body);
273
        $instance->peek();
274
275
        return $instance;
276
    }
277
278
    /**
279
     * Boot a new instance
280
     */
281
    public function boot(){
282
        $this->attributes = [];
283
284
        $this->config = ClientManager::get('options');
285
        $this->available_flags = ClientManager::get('flags');
286
287
        $this->attachments = AttachmentCollection::make([]);
288
        $this->flags = FlagCollection::make([]);
289
    }
290
291
    /**
292
     * Call dynamic attribute setter and getter methods
293
     * @param string $method
294
     * @param array $arguments
295
     *
296
     * @return mixed
297
     * @throws MethodNotFoundException
298
     */
299
    public function __call($method, $arguments) {
300
        if(strtolower(substr($method, 0, 3)) === 'get') {
301
            $name = Str::snake(substr($method, 3));
302
            return $this->get($name);
303
        }elseif (strtolower(substr($method, 0, 3)) === 'set') {
304
            $name = Str::snake(substr($method, 3));
305
306
            if(in_array($name, array_keys($this->attributes))) {
307
                return $this->__set($name, array_pop($arguments));
308
            }
309
310
        }
311
312
        throw new MethodNotFoundException("Method ".self::class.'::'.$method.'() is not supported');
313
    }
314
315
    /**
316
     * Magic setter
317
     * @param $name
318
     * @param $value
319
     *
320
     * @return mixed
321
     */
322
    public function __set($name, $value) {
323
        $this->attributes[$name] = $value;
324
325
        return $this->attributes[$name];
326
    }
327
328
    /**
329
     * Magic getter
330
     * @param $name
331
     *
332
     * @return Attribute|mixed|null
333
     */
334
    public function __get($name) {
335
        return $this->get($name);
336
    }
337
338
    /**
339
     * Get an available message or message header attribute
340
     * @param $name
341
     *
342
     * @return Attribute|mixed|null
343
     */
344
    public function get($name) {
345
        if(isset($this->attributes[$name])) {
346
            return $this->attributes[$name];
347
        }
348
349
        return $this->header->get($name);
350
    }
351
352
    /**
353
     * Check if the Message has a text body
354
     *
355
     * @return bool
356
     */
357
    public function hasTextBody() {
358
        return isset($this->bodies['text']);
359
    }
360
361
    /**
362
     * Get the Message text body
363
     *
364
     * @return mixed
365
     */
366
    public function getTextBody() {
367
        if (!isset($this->bodies['text'])) {
368
            return null;
369
        }
370
371
        return $this->bodies['text'];
372
    }
373
374
    /**
375
     * Check if the Message has a html body
376
     *
377
     * @return bool
378
     */
379
    public function hasHTMLBody() {
380
        return isset($this->bodies['html']);
381
    }
382
383
    /**
384
     * Get the Message html body
385
     *
386
     * @return string|null
387
     */
388
    public function getHTMLBody() {
389
        if (!isset($this->bodies['html'])) {
390
            return null;
391
        }
392
393
        return $this->bodies['html'];
394
    }
395
396
    /**
397
     * Parse all defined headers
398
     *
399
     * @throws Exceptions\ConnectionFailedException
400
     * @throws Exceptions\RuntimeException
401
     * @throws InvalidMessageDateException
402
     * @throws MessageHeaderFetchingException
403
     */
404
    private function parseHeader() {
405
        $sequence_id = $this->getSequenceId();
406
        $headers = $this->client->getConnection()->headers([$sequence_id], "RFC822", $this->sequence === IMAP::ST_UID);
407
        if (!isset($headers[$sequence_id])) {
408
            throw new MessageHeaderFetchingException("no headers found", 0);
409
        }
410
411
        $this->parseRawHeader($headers[$sequence_id]);
412
    }
413
414
    /**
415
     * @param string $raw_header
416
     *
417
     * @throws InvalidMessageDateException
418
     */
419
    public function parseRawHeader($raw_header){
420
        $this->header = new Header($raw_header);
421
    }
422
423
    /**
424
     * Parse additional raw flags
425
     * @param array $raw_flags
426
     */
427
    public function parseRawFlags($raw_flags) {
428
        $this->flags = FlagCollection::make([]);
429
430
        foreach($raw_flags as $flag) {
431
            if (strpos($flag, "\\") === 0){
432
                $flag = substr($flag, 1);
433
            }
434
            $flag_key = strtolower($flag);
435
            if ($this->available_flags === null || in_array($flag_key, $this->available_flags)) {
436
                $this->flags->put($flag_key, $flag);
437
            }
438
        }
439
    }
440
441
    /**
442
     * Parse additional flags
443
     *
444
     * @return void
445
     * @throws Exceptions\ConnectionFailedException
446
     * @throws MessageFlagException
447
     * @throws Exceptions\RuntimeException
448
     */
449
    private function parseFlags() {
450
        $this->client->openFolder($this->folder_path);
451
        $this->flags = FlagCollection::make([]);
452
453
        $sequence_id = $this->getSequenceId();
454
        try {
455
            $flags = $this->client->getConnection()->flags([$sequence_id], $this->sequence === IMAP::ST_UID);
456
        } catch (Exceptions\RuntimeException $e) {
457
            throw new MessageFlagException("flag could not be fetched", 0, $e);
458
        }
459
460
        if (isset($flags[$sequence_id])) {
461
            $this->parseRawFlags($flags[$sequence_id]);
462
        }
463
    }
464
465
    /**
466
     * Parse the Message body
467
     *
468
     * @return $this
469
     * @throws Exceptions\ConnectionFailedException
470
     * @throws Exceptions\MessageContentFetchingException
471
     * @throws InvalidMessageDateException
472
     * @throws Exceptions\EventNotFoundException
473
     * @throws MessageFlagException
474
     * @throws Exceptions\RuntimeException
475
     */
476
    public function parseBody() {
477
        $this->client->openFolder($this->folder_path);
478
479
        $sequence_id = $this->getSequenceId();
480
        try {
481
            $contents = $this->client->getConnection()->content([$sequence_id], "RFC822", $this->sequence === IMAP::ST_UID);
482
        } catch (Exceptions\RuntimeException $e) {
483
            throw new MessageContentFetchingException("failed to fetch content", 0);
484
        }
485
        if (!isset($contents[$sequence_id])) {
486
            throw new MessageContentFetchingException("no content found", 0);
487
        }
488
        $content = $contents[$sequence_id];
489
490
        $body = $this->parseRawBody($content);
491
        $this->peek();
492
493
        return $body;
494
    }
495
496
    /**
497
     * Handle auto "Seen" flag handling
498
     *
499
     * @throws Exceptions\ConnectionFailedException
500
     * @throws Exceptions\EventNotFoundException
501
     * @throws MessageFlagException
502
     * @throws Exceptions\RuntimeException
503
     */
504
    public function peek(){
505
        if ($this->fetch_options == IMAP::FT_PEEK) {
506
            if ($this->getFlags()->get("seen") == null) {
507
                $this->unsetFlag("Seen");
508
            }
509
        }elseif ($this->getFlags()->get("seen") != null) {
510
            $this->setFlag("Seen");
511
        }
512
    }
513
514
    /**
515
     * Parse a given message body
516
     * @param string $raw_body
517
     *
518
     * @return $this
519
     * @throws Exceptions\ConnectionFailedException
520
     * @throws InvalidMessageDateException
521
     * @throws MessageContentFetchingException
522
     * @throws Exceptions\RuntimeException
523
     */
524
    public function parseRawBody($raw_body) {
525
        $this->structure = new Structure($raw_body, $this->header);
526
        $this->fetchStructure($this->structure);
527
528
        return $this;
529
    }
530
531
    /**
532
     * Fetch the Message structure
533
     * @param Structure $structure
534
     *
535
     * @throws Exceptions\ConnectionFailedException
536
     * @throws Exceptions\RuntimeException
537
     */
538
    private function fetchStructure($structure) {
539
        $this->client->openFolder($this->folder_path);
540
541
        foreach ($structure->parts as $part) {
542
            $this->fetchPart($part);
543
        }
544
    }
545
546
    /**
547
     * Fetch a given part
548
     * @param Part $part
549
     */
550
    private function fetchPart(Part $part) {
551
        if ($part->isAttachment()) {
552
            $this->fetchAttachment($part);
553
        }else{
554
            $encoding = $this->getEncoding($part);
555
556
            $content = $this->decodeString($part->content, $part->encoding);
557
558
            // We don't need to do convertEncoding() if charset is ASCII (us-ascii):
559
            //     ASCII is a subset of UTF-8, so all ASCII files are already UTF-8 encoded
560
            //     https://stackoverflow.com/a/11303410
561
            //
562
            // us-ascii is the same as ASCII:
563
            //     ASCII is the traditional name for the encoding system; the Internet Assigned Numbers Authority (IANA)
564
            //     prefers the updated name US-ASCII, which clarifies that this system was developed in the US and
565
            //     based on the typographical symbols predominantly in use there.
566
            //     https://en.wikipedia.org/wiki/ASCII
567
            //
568
            // convertEncoding() function basically means convertToUtf8(), so when we convert ASCII string into UTF-8 it gets broken.
569
            if ($encoding != 'us-ascii') {
570
                $content = $this->convertEncoding($content, $encoding);
571
            }
572
573
            $subtype = strtolower($part->subtype);
574
            $subtype = $subtype == "plain" || $subtype == "" ? "text" : $subtype;
575
576
            if (isset($this->bodies[$subtype])) {
577
                $this->bodies[$subtype] .= "\n".$content;
0 ignored issues
show
Bug introduced by
Are you sure $content of type array|false|string can be used in concatenation? ( Ignorable by Annotation )

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

577
                $this->bodies[$subtype] .= "\n"./** @scrutinizer ignore-type */ $content;
Loading history...
578
            }else{
579
                $this->bodies[$subtype] = $content;
580
            }
581
        }
582
    }
583
584
    /**
585
     * Fetch the Message attachment
586
     * @param Part $part
587
     */
588
    protected function fetchAttachment($part) {
589
        $oAttachment = new Attachment($this, $part);
590
591
        if ($oAttachment->getName() !== null && $oAttachment->getSize() > 0) {
592
            if ($oAttachment->getId() !== null) {
0 ignored issues
show
introduced by
The condition $oAttachment->getId() !== null is always true.
Loading history...
593
                $this->attachments->put($oAttachment->getId(), $oAttachment);
594
            } else {
595
                $this->attachments->push($oAttachment);
596
            }
597
        }
598
    }
599
600
    /**
601
     * Fail proof setter for $fetch_option
602
     * @param $option
603
     *
604
     * @return $this
605
     */
606
    public function setFetchOption($option) {
607
        if (is_long($option) === true) {
608
            $this->fetch_options = $option;
609
        } elseif (is_null($option) === true) {
610
            $config = ClientManager::get('options.fetch', IMAP::FT_UID);
611
            $this->fetch_options = is_long($config) ? $config : 1;
612
        }
613
614
        return $this;
615
    }
616
617
    /**
618
     * Set the sequence type
619
     * @param int $sequence
620
     *
621
     * @return $this
622
     */
623
    public function setSequence($sequence) {
624
        if (is_long($sequence)) {
0 ignored issues
show
introduced by
The condition is_long($sequence) is always true.
Loading history...
625
            $this->sequence = $sequence;
626
        } elseif (is_null($sequence)) {
627
            $config = ClientManager::get('options.sequence', IMAP::ST_MSGN);
628
            $this->sequence = is_long($config) ? $config : IMAP::ST_MSGN;
629
        }
630
631
        return $this;
632
    }
633
634
    /**
635
     * Fail proof setter for $fetch_body
636
     * @param $option
637
     *
638
     * @return $this
639
     */
640
    public function setFetchBodyOption($option) {
641
        if (is_bool($option)) {
642
            $this->fetch_body = $option;
643
        } elseif (is_null($option)) {
644
            $config = ClientManager::get('options.fetch_body', true);
645
            $this->fetch_body = is_bool($config) ? $config : true;
646
        }
647
648
        return $this;
649
    }
650
651
    /**
652
     * Fail proof setter for $fetch_flags
653
     * @param $option
654
     *
655
     * @return $this
656
     */
657
    public function setFetchFlagsOption($option) {
658
        if (is_bool($option)) {
659
            $this->fetch_flags = $option;
660
        } elseif (is_null($option)) {
661
            $config = ClientManager::get('options.fetch_flags', true);
662
            $this->fetch_flags = is_bool($config) ? $config : true;
663
        }
664
665
        return $this;
666
    }
667
668
    /**
669
     * Decode a given string
670
     * @param $string
671
     * @param $encoding
672
     *
673
     * @return string
674
     */
675
    public function decodeString($string, $encoding) {
676
        switch ($encoding) {
677
            case IMAP::MESSAGE_ENC_BINARY:
678
                if (extension_loaded('imap')) {
679
                    return base64_decode(\imap_binary($string));
680
                }
681
                return base64_decode($string);
682
            case IMAP::MESSAGE_ENC_BASE64:
683
                return base64_decode($string);
684
            case IMAP::MESSAGE_ENC_8BIT:
685
            case IMAP::MESSAGE_ENC_QUOTED_PRINTABLE:
686
                return quoted_printable_decode($string);
687
            case IMAP::MESSAGE_ENC_7BIT:
688
            case IMAP::MESSAGE_ENC_OTHER:
689
            default:
690
                return $string;
691
        }
692
    }
693
694
    /**
695
     * Convert the encoding
696
     * @param $str
697
     * @param string $from
698
     * @param string $to
699
     *
700
     * @return mixed|string
701
     */
702
    public function convertEncoding($str, $from = "ISO-8859-2", $to = "UTF-8") {
703
704
        $from = EncodingAliases::get($from);
705
        $to = EncodingAliases::get($to);
706
707
        if ($from === $to) {
708
            return $str;
709
        }
710
711
        // We don't need to do convertEncoding() if charset is ASCII (us-ascii):
712
        //     ASCII is a subset of UTF-8, so all ASCII files are already UTF-8 encoded
713
        //     https://stackoverflow.com/a/11303410
714
        //
715
        // us-ascii is the same as ASCII:
716
        //     ASCII is the traditional name for the encoding system; the Internet Assigned Numbers Authority (IANA)
717
        //     prefers the updated name US-ASCII, which clarifies that this system was developed in the US and
718
        //     based on the typographical symbols predominantly in use there.
719
        //     https://en.wikipedia.org/wiki/ASCII
720
        //
721
        // convertEncoding() function basically means convertToUtf8(), so when we convert ASCII string into UTF-8 it gets broken.
722
        if (strtolower($from) == 'us-ascii' && $to == 'UTF-8') {
723
            return $str;
724
        }
725
726
        if (function_exists('iconv') && $from != 'UTF-7' && $to != 'UTF-7') {
727
            return @iconv($from, $to.'//IGNORE', $str);
728
        } else {
729
            if (!$from) {
730
                return mb_convert_encoding($str, $to);
731
            }
732
            return mb_convert_encoding($str, $to, $from);
733
        }
734
    }
735
736
    /**
737
     * Get the encoding of a given abject
738
     * @param object|string $structure
739
     *
740
     * @return string
741
     */
742
    public function getEncoding($structure) {
743
        if (property_exists($structure, 'parameters')) {
744
            foreach ($structure->parameters as $parameter) {
745
                if (strtolower($parameter->attribute) == "charset") {
746
                    return EncodingAliases::get($parameter->value);
747
                }
748
            }
749
        }elseif (property_exists($structure, 'charset')){
750
            return EncodingAliases::get($structure->charset);
751
        }elseif (is_string($structure) === true){
752
            return mb_detect_encoding($structure);
753
        }
754
755
        return 'UTF-8';
756
    }
757
758
    /**
759
     * Get the messages folder
760
     *
761
     * @return mixed
762
     * @throws Exceptions\ConnectionFailedException
763
     * @throws Exceptions\FolderFetchingException
764
     * @throws Exceptions\RuntimeException
765
     */
766
    public function getFolder(){
767
        return $this->client->getFolderByPath($this->folder_path);
768
    }
769
770
    /**
771
     * Create a message thread based on the current message
772
     * @param Folder|null $sent_folder
773
     * @param MessageCollection|null $thread
774
     * @param Folder|null $folder
775
     *
776
     * @return MessageCollection|null
777
     * @throws Exceptions\ConnectionFailedException
778
     * @throws Exceptions\FolderFetchingException
779
     * @throws Exceptions\GetMessagesFailedException
780
     * @throws Exceptions\RuntimeException
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->getFolderByPath(ClientManager::get("options.common_folders.sent", "INBOX/Sent"));
786
787
        /** @var Message $message */
788
        foreach($thread as $message) {
789
            if ($message->message_id->first() == $this->message_id->first()) {
790
                return $thread;
791
            }
792
        }
793
        $thread->push($this);
794
795
        $this->fetchThreadByInReplyTo($thread, $this->message_id, $folder, $folder, $sent_folder);
796
        $this->fetchThreadByInReplyTo($thread, $this->message_id, $sent_folder, $folder, $sent_folder);
797
798
        if (is_array($this->in_reply_to)) {
0 ignored issues
show
introduced by
The condition is_array($this->in_reply_to) is always false.
Loading history...
799
            foreach($this->in_reply_to as $in_reply_to) {
800
                $this->fetchThreadByMessageId($thread, $in_reply_to, $folder, $folder, $sent_folder);
801
                $this->fetchThreadByMessageId($thread, $in_reply_to, $sent_folder, $folder, $sent_folder);
802
            }
803
        }
804
805
        return $thread;
806
    }
807
808
    /**
809
     * Fetch a partial thread by message id
810
     * @param MessageCollection $thread
811
     * @param string $in_reply_to
812
     * @param Folder $primary_folder
813
     * @param Folder $secondary_folder
814
     * @param Folder $sent_folder
815
     *
816
     * @throws Exceptions\ConnectionFailedException
817
     * @throws Exceptions\GetMessagesFailedException
818
     * @throws Exceptions\RuntimeException
819
     */
820
    protected function fetchThreadByInReplyTo(&$thread, $in_reply_to, $primary_folder, $secondary_folder, $sent_folder){
821
        $primary_folder->query()->inReplyTo($in_reply_to)
822
        ->setFetchBody($this->getFetchBodyOption())
823
        ->leaveUnread()->get()->each(function($message) use(&$thread, $secondary_folder, $sent_folder){
824
            /** @var Message $message */
825
            $message->thread($sent_folder, $thread, $secondary_folder);
826
        });
827
    }
828
829
    /**
830
     * Fetch a partial thread by message id
831
     * @param MessageCollection $thread
832
     * @param string $message_id
833
     * @param Folder $primary_folder
834
     * @param Folder $secondary_folder
835
     * @param Folder $sent_folder
836
     *
837
     * @throws Exceptions\ConnectionFailedException
838
     * @throws Exceptions\GetMessagesFailedException
839
     * @throws Exceptions\RuntimeException
840
     */
841
    protected function fetchThreadByMessageId(&$thread, $message_id, $primary_folder, $secondary_folder, $sent_folder){
842
        $primary_folder->query()->messageId($message_id)
843
        ->setFetchBody($this->getFetchBodyOption())
844
        ->leaveUnread()->get()->each(function($message) use(&$thread, $secondary_folder, $sent_folder){
845
            /** @var Message $message */
846
            $message->thread($sent_folder, $thread, $secondary_folder);
847
        });
848
    }
849
850
    /**
851
     * Copy the current Messages to a mailbox
852
     * @param string $folder_path
853
     * @param boolean $expunge
854
     *
855
     * @return null|Message
856
     * @throws Exceptions\ConnectionFailedException
857
     * @throws Exceptions\FolderFetchingException
858
     * @throws Exceptions\RuntimeException
859
     * @throws InvalidMessageDateException
860
     * @throws MessageContentFetchingException
861
     * @throws MessageHeaderFetchingException
862
     * @throws Exceptions\EventNotFoundException
863
     * @throws MessageFlagException
864
     * @throws Exceptions\MessageNotFoundException
865
     */
866
    public function copy($folder_path, $expunge = false) {
867
        $this->client->openFolder($folder_path);
868
        $status = $this->client->getConnection()->examineFolder($folder_path);
869
870
        if (isset($status["uidnext"])) {
871
            $next_uid = $status["uidnext"];
872
873
            /** @var Folder $folder */
874
            $folder = $this->client->getFolderByPath($folder_path);
875
876
            $this->client->openFolder($this->folder_path);
877
            if ($this->client->getConnection()->copyMessage($folder->path, $this->getSequenceId(), null, $this->sequence === IMAP::ST_UID) == true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
878
                return $this->fetchNewMail($folder, $next_uid, "copied", $expunge);
879
            }
880
        }
881
882
        return null;
883
    }
884
885
    /**
886
     * Move the current Messages to a mailbox
887
     * @param string $folder_path
888
     * @param boolean $expunge
889
     *
890
     * @return Message|null
891
     * @throws Exceptions\ConnectionFailedException
892
     * @throws Exceptions\FolderFetchingException
893
     * @throws Exceptions\RuntimeException
894
     * @throws InvalidMessageDateException
895
     * @throws MessageContentFetchingException
896
     * @throws MessageHeaderFetchingException
897
     * @throws Exceptions\EventNotFoundException
898
     * @throws MessageFlagException
899
     * @throws Exceptions\MessageNotFoundException
900
     */
901
    public function move($folder_path, $expunge = false) {
902
        $this->client->openFolder($folder_path);
903
        $status = $this->client->getConnection()->examineFolder($folder_path);
904
905
        if (isset($status["uidnext"])) {
906
            $next_uid = $status["uidnext"];
907
908
            /** @var Folder $folder */
909
            $folder = $this->client->getFolderByPath($folder_path);
910
911
            $this->client->openFolder($this->folder_path);
912
            if ($this->client->getConnection()->moveMessage($folder->path, $this->getSequenceId(), null, $this->sequence === IMAP::ST_UID) == true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
913
                return $this->fetchNewMail($folder, $next_uid, "moved", $expunge);
914
            }
915
        }
916
917
        return null;
918
    }
919
920
    /**
921
     * Fetch a new message and fire a given event
922
     * @param Folder $folder
923
     * @param int $next_uid
924
     * @param string $event
925
     * @param boolean $expunge
926
     *
927
     * @return mixed
928
     * @throws Exceptions\ConnectionFailedException
929
     * @throws Exceptions\EventNotFoundException
930
     * @throws Exceptions\MessageNotFoundException
931
     * @throws Exceptions\RuntimeException
932
     * @throws InvalidMessageDateException
933
     * @throws MessageContentFetchingException
934
     * @throws MessageFlagException
935
     * @throws MessageHeaderFetchingException
936
     */
937
    protected function fetchNewMail($folder, $next_uid, $event, $expunge){
938
        if($expunge) $this->client->expunge();
939
940
        $this->client->openFolder($folder->path);
941
942
        if ($this->sequence === IMAP::ST_UID) {
943
            $sequence_id = $next_uid;
944
        }else{
945
            $sequence_id = $this->client->getConnection()->getMessageNumber($next_uid);
946
        }
947
948
        $message = $folder->query()->getMessage($sequence_id, null, $this->sequence);
949
        $event = $this->getEvent("message", $event);
950
        $event::dispatch($this, $message);
951
952
        return $message;
953
    }
954
955
    /**
956
     * Delete the current Message
957
     * @param bool $expunge
958
     *
959
     * @return bool
960
     * @throws Exceptions\ConnectionFailedException
961
     * @throws Exceptions\EventNotFoundException
962
     * @throws MessageFlagException
963
     * @throws Exceptions\RuntimeException
964
     */
965
    public function delete($expunge = true) {
966
        $status = $this->setFlag("Deleted");
967
        if($expunge) $this->client->expunge();
968
969
        $event = $this->getEvent("message", "deleted");
970
        $event::dispatch($this);
971
972
        return $status;
973
    }
974
975
    /**
976
     * Restore a deleted Message
977
     * @param boolean $expunge
978
     *
979
     * @return bool
980
     * @throws Exceptions\ConnectionFailedException
981
     * @throws Exceptions\EventNotFoundException
982
     * @throws MessageFlagException
983
     * @throws Exceptions\RuntimeException
984
     */
985
    public function restore($expunge = true) {
986
        $status = $this->unsetFlag("Deleted");
987
        if($expunge) $this->client->expunge();
988
989
        $event = $this->getEvent("message", "restored");
990
        $event::dispatch($this);
991
992
        return $status;
993
    }
994
995
    /**
996
     * Set a given flag
997
     * @param string|array $flag
998
     *
999
     * @return bool
1000
     * @throws Exceptions\ConnectionFailedException
1001
     * @throws MessageFlagException
1002
     * @throws Exceptions\EventNotFoundException
1003
     * @throws Exceptions\RuntimeException
1004
     */
1005
    public function setFlag($flag) {
1006
        $this->client->openFolder($this->folder_path);
1007
        $flag = "\\".trim(is_array($flag) ? implode(" \\", $flag) : $flag);
1008
        $sequence_id = $this->getSequenceId();
1009
        try {
1010
            $status = $this->client->getConnection()->store([$flag], $sequence_id, $sequence_id, "+", true, $this->sequence === IMAP::ST_UID);
1011
        } catch (Exceptions\RuntimeException $e) {
1012
            throw new MessageFlagException("flag could not be set", 0, $e);
1013
        }
1014
        $this->parseFlags();
1015
1016
        $event = $this->getEvent("flag", "new");
1017
        $event::dispatch($this, $flag);
1018
1019
        return $status;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $status also could return the type array which is incompatible with the documented return type boolean.
Loading history...
1020
    }
1021
1022
    /**
1023
     * Unset a given flag
1024
     * @param string|array $flag
1025
     *
1026
     * @return bool
1027
     * @throws Exceptions\ConnectionFailedException
1028
     * @throws Exceptions\EventNotFoundException
1029
     * @throws MessageFlagException
1030
     * @throws Exceptions\RuntimeException
1031
     */
1032
    public function unsetFlag($flag) {
1033
        $this->client->openFolder($this->folder_path);
1034
1035
        $flag = "\\".trim(is_array($flag) ? implode(" \\", $flag) : $flag);
1036
        $sequence_id = $this->getSequenceId();
1037
        try {
1038
            $status = $this->client->getConnection()->store([$flag], $sequence_id, $sequence_id, "-", true, $this->sequence === IMAP::ST_UID);
1039
        } catch (Exceptions\RuntimeException $e) {
1040
            throw new MessageFlagException("flag could not be removed", 0, $e);
1041
        }
1042
        $this->parseFlags();
1043
1044
        $event = $this->getEvent("flag", "deleted");
1045
        $event::dispatch($this, $flag);
1046
1047
        return $status;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $status also could return the type array which is incompatible with the documented return type boolean.
Loading history...
1048
    }
1049
1050
    /**
1051
     * Set a given flag
1052
     * @param string|array $flag
1053
     *
1054
     * @return bool
1055
     * @throws Exceptions\ConnectionFailedException
1056
     * @throws MessageFlagException
1057
     * @throws Exceptions\EventNotFoundException
1058
     * @throws Exceptions\RuntimeException
1059
     */
1060
    public function addFlag($flag) {
1061
        return $this->setFlag($flag);
1062
    }
1063
1064
    /**
1065
     * Unset a given flag
1066
     * @param string|array $flag
1067
     *
1068
     * @return bool
1069
     * @throws Exceptions\ConnectionFailedException
1070
     * @throws Exceptions\EventNotFoundException
1071
     * @throws MessageFlagException
1072
     * @throws Exceptions\RuntimeException
1073
     */
1074
    public function removeFlag($flag) {
1075
        return $this->unsetFlag($flag);
1076
    }
1077
1078
    /**
1079
     * Get all message attachments.
1080
     *
1081
     * @return AttachmentCollection
1082
     */
1083
    public function getAttachments() {
1084
        return $this->attachments;
1085
    }
1086
1087
    /**
1088
     * Get all message attachments.
1089
     *
1090
     * @return AttachmentCollection
1091
     */
1092
    public function attachments(){
1093
        return $this->getAttachments();
1094
    }
1095
1096
    /**
1097
     * Checks if there are any attachments present
1098
     *
1099
     * @return boolean
1100
     */
1101
    public function hasAttachments() {
1102
        return $this->attachments->isEmpty() === false;
1103
    }
1104
1105
    /**
1106
     * Get the raw body
1107
     *
1108
     * @return string
1109
     * @throws Exceptions\ConnectionFailedException
1110
     * @throws Exceptions\RuntimeException
1111
     */
1112
    public function getRawBody() {
1113
        if ($this->raw_body === null) {
1114
            $this->client->openFolder($this->folder_path);
1115
1116
            $this->raw_body = $this->structure->raw;
1117
        }
1118
1119
        return $this->raw_body;
1120
    }
1121
1122
    /**
1123
     * Get the message header
1124
     *
1125
     * @return Header
1126
     */
1127
    public function getHeader() {
1128
        return $this->header;
1129
    }
1130
1131
    /**
1132
     * Get the current client
1133
     *
1134
     * @return Client
1135
     */
1136
    public function getClient() {
1137
        return $this->client;
1138
    }
1139
1140
    /**
1141
     * Get the used fetch option
1142
     *
1143
     * @return integer
1144
     */
1145
    public function getFetchOptions() {
1146
        return $this->fetch_options;
1147
    }
1148
1149
    /**
1150
     * Get the used fetch body option
1151
     *
1152
     * @return boolean
1153
     */
1154
    public function getFetchBodyOption() {
1155
        return $this->fetch_body;
1156
    }
1157
1158
    /**
1159
     * Get the used fetch flags option
1160
     *
1161
     * @return boolean
1162
     */
1163
    public function getFetchFlagsOption() {
1164
        return $this->fetch_flags;
1165
    }
1166
1167
    /**
1168
     * Get all available bodies
1169
     *
1170
     * @return array
1171
     */
1172
    public function getBodies() {
1173
        return $this->bodies;
1174
    }
1175
1176
    /**
1177
     * Get all set flags
1178
     *
1179
     * @return FlagCollection
1180
     */
1181
    public function getFlags() {
1182
        return $this->flags;
1183
    }
1184
1185
    /**
1186
     * Get all set flags
1187
     *
1188
     * @return FlagCollection
1189
     */
1190
    public function flags(){
1191
        return $this->getFlags();
1192
    }
1193
1194
    /**
1195
     * Get the fetched structure
1196
     *
1197
     * @return Structure|null
1198
     */
1199
    public function getStructure(){
1200
        return $this->structure;
1201
    }
1202
1203
    /**
1204
     * Check if a message matches an other by comparing basic attributes
1205
     *
1206
     * @param  null|Message $message
1207
     * @return boolean
1208
     */
1209
    public function is(Message $message = null) {
1210
        if (is_null($message)) {
1211
            return false;
1212
        }
1213
1214
        return $this->uid == $message->uid
1215
            && $this->message_id->first() == $message->message_id->first()
1216
            && $this->subject->first() == $message->subject->first()
1217
            && $this->date->toDate()->eq($message->date);
1218
    }
1219
1220
    /**
1221
     * Get all message attributes
1222
     *
1223
     * @return array
1224
     */
1225
    public function getAttributes(){
1226
        return array_merge($this->attributes, $this->header->getAttributes());
1227
    }
1228
1229
    /**
1230
     * Set the message mask
1231
     * @param $mask
1232
     *
1233
     * @return $this
1234
     */
1235
    public function setMask($mask){
1236
        if(class_exists($mask)){
1237
            $this->mask = $mask;
1238
        }
1239
1240
        return $this;
1241
    }
1242
1243
    /**
1244
     * Get the used message mask
1245
     *
1246
     * @return string
1247
     */
1248
    public function getMask(){
1249
        return $this->mask;
1250
    }
1251
1252
    /**
1253
     * Get a masked instance by providing a mask name
1254
     * @param string|null $mask
1255
     *
1256
     * @return mixed
1257
     * @throws MaskNotFoundException
1258
     */
1259
    public function mask($mask = null){
1260
        $mask = $mask !== null ? $mask : $this->mask;
1261
        if(class_exists($mask)){
1262
            return new $mask($this);
1263
        }
1264
1265
        throw new MaskNotFoundException("Unknown mask provided: ".$mask);
1266
    }
1267
1268
    /**
1269
     * Get the message path aka folder path
1270
     *
1271
     * @return string
1272
     */
1273
    public function getFolderPath(){
1274
        return $this->folder_path;
1275
    }
1276
1277
    /**
1278
     * Set the message path aka folder path
1279
     * @param $folder_path
1280
     *
1281
     * @return $this
1282
     */
1283
    public function setFolderPath($folder_path){
1284
        $this->folder_path = $folder_path;
1285
1286
        return $this;
1287
    }
1288
1289
    /**
1290
     * Set the config
1291
     * @param $config
1292
     *
1293
     * @return $this
1294
     */
1295
    public function setConfig($config){
1296
        $this->config = $config;
1297
1298
        return $this;
1299
    }
1300
1301
    /**
1302
     * Set the available flags
1303
     * @param $available_flags
1304
     *
1305
     * @return $this
1306
     */
1307
    public function setAvailableFlags($available_flags){
1308
        $this->available_flags = $available_flags;
1309
1310
        return $this;
1311
    }
1312
1313
    /**
1314
     * Set the attachment collection
1315
     * @param $attachments
1316
     *
1317
     * @return $this
1318
     */
1319
    public function setAttachments($attachments){
1320
        $this->attachments = $attachments;
1321
1322
        return $this;
1323
    }
1324
1325
    /**
1326
     * Set the flag collection
1327
     * @param $flags
1328
     *
1329
     * @return $this
1330
     */
1331
    public function setFlags($flags){
1332
        $this->flags = $flags;
1333
1334
        return $this;
1335
    }
1336
1337
    /**
1338
     * Set the client
1339
     * @param $client
1340
     *
1341
     * @return $this
1342
     * @throws Exceptions\RuntimeException
1343
     * @throws Exceptions\ConnectionFailedException
1344
     */
1345
    public function setClient($client){
1346
        $this->client = $client;
1347
        $this->client->openFolder($this->folder_path);
1348
1349
        return $this;
1350
    }
1351
1352
    /**
1353
     * Set the message number
1354
     * @param int $uid
1355
     *
1356
     * @return $this
1357
     * @throws Exceptions\MessageNotFoundException
1358
     * @throws Exceptions\ConnectionFailedException
1359
     */
1360
    public function setUid($uid){
1361
        $this->uid = $uid;
1362
        $this->msgn = $this->client->getConnection()->getMessageNumber($this->uid);
1363
        $this->msglist = null;
1364
1365
        return $this;
1366
    }
1367
1368
    /**
1369
     * Set the message number
1370
     * @param $msgn
1371
     * @param int|null $msglist
1372
     *
1373
     * @return $this
1374
     * @throws Exceptions\MessageNotFoundException
1375
     * @throws Exceptions\ConnectionFailedException
1376
     */
1377
    public function setMsgn($msgn, $msglist = null){
1378
        $this->msgn = $msgn;
1379
        $this->msglist = $msglist;
1380
        $this->uid = $this->client->getConnection()->getUid($this->msgn);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->client->getConnec...()->getUid($this->msgn) of type string or array is incompatible with the declared type integer of property $uid.

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...
1381
1382
        return $this;
1383
    }
1384
1385
    /**
1386
     * Get the current sequence type
1387
     *
1388
     * @return int
1389
     */
1390
    public function getSequence(){
1391
        return $this->sequence;
1392
    }
1393
1394
    /**
1395
     * Set the sequence type
1396
     *
1397
     * @return int
1398
     */
1399
    public function getSequenceId(){
1400
        return $this->sequence === IMAP::ST_UID ? $this->uid : $this->msgn;
1401
    }
1402
1403
    /**
1404
     * Set the sequence id
1405
     * @param $uid
1406
     * @param int|null $msglist
1407
     *
1408
     * @throws Exceptions\ConnectionFailedException
1409
     * @throws Exceptions\MessageNotFoundException
1410
     */
1411
    public function setSequenceId($uid, $msglist = null){
1412
        if ($this->getSequence() === IMAP::ST_UID) {
1413
            $this->setUid($uid);
1414
            $this->setMsglist($msglist);
1415
        }else{
1416
            $this->setMsgn($uid, $msglist);
1417
        }
1418
    }
1419
}
1420