Passed
Push — master ( 4b6ca8...7b77e0 )
by Malte
02:34
created

Message::move()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 26
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 15
c 2
b 0
f 0
dl 0
loc 26
rs 9.7666
cc 4
nc 4
nop 2
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 setUid(integer $uid)
53
 * @method integer getMsgn()
54
 * @method integer getPriority()
55
 * @method integer setPriority(integer $priority)
56
 * @method string getSubject()
57
 * @method string setSubject(string $subject)
58
 * @method string getMessageId()
59
 * @method string setMessageId(string $message_id)
60
 * @method string getMessageNo()
61
 * @method string setMessageNo(string $message_no)
62
 * @method string getReferences()
63
 * @method string setReferences(string $references)
64
 * @method carbon getDate()
65
 * @method carbon setDate(carbon $date)
66
 * @method array getFrom()
67
 * @method array setFrom(array $from)
68
 * @method array getTo()
69
 * @method array setTo(array $to)
70
 * @method array getCc()
71
 * @method array setCc(array $cc)
72
 * @method array getBcc()
73
 * @method array setBcc(array $bcc)
74
 * @method array getReplyTo()
75
 * @method array setReplyTo(array $reply_to)
76
 * @method array getInReplyTo()
77
 * @method array setInReplyTo(array $in_reply_to)
78
 * @method array getSender()
79
 * @method array setSender(array $sender)
80
 */
81
class Message {
82
    use HasEvents;
83
84
    /**
85
     * Client instance
86
     *
87
     * @var Client
88
     */
89
    private $client = Client::class;
90
91
    /**
92
     * Default mask
93
     *
94
     * @var string $mask
95
     */
96
    protected $mask = MessageMask::class;
97
98
    /**
99
     * Used config
100
     *
101
     * @var array $config
102
     */
103
    protected $config = [];
104
105
    /**
106
     * Attribute holder
107
     *
108
     * @var array $attributes
109
     */
110
    protected $attributes = [
111
        'message_no' => null,
112
    ];
113
114
    /**
115
     * The message folder path
116
     *
117
     * @var string $folder_path
118
     */
119
    protected $folder_path;
120
121
    /**
122
     * Fetch body options
123
     *
124
     * @var integer
125
     */
126
    public $fetch_options = null;
127
128
    /**
129
     * Fetch body options
130
     *
131
     * @var bool
132
     */
133
    public $fetch_body = null;
134
135
    /**
136
     * Fetch flags options
137
     *
138
     * @var bool
139
     */
140
    public $fetch_flags = null;
141
142
    /**
143
     * @var Header $header
144
     */
145
    public $header = null;
146
147
    /**
148
     * Raw message body
149
     *
150
     * @var null|string $raw_body
151
     */
152
    public $raw_body = null;
153
154
    /**
155
     * Message structure
156
     *
157
     * @var Structure $structure
158
     */
159
    protected $structure = null;
160
161
    /**
162
     * Message body components
163
     *
164
     * @var array   $bodies
165
     * @var AttachmentCollection|array $attachments
166
     * @var FlagCollection|array       $flags
167
     */
168
    public $bodies = [];
169
    public $attachments = [];
170
    public $flags = [];
171
172
    /**
173
     * A list of all available and supported flags
174
     *
175
     * @var array $available_flags
176
     */
177
    private $available_flags = ['recent', 'flagged', 'answered', 'deleted', 'seen', 'draft'];
178
179
    /**
180
     * Message constructor.
181
     * @param integer $msgn
182
     * @param integer|null $msglist
183
     * @param Client $client
184
     * @param integer|null $fetch_options
185
     * @param boolean $fetch_body
186
     * @param boolean $fetch_flags
187
     *
188
     * @throws Exceptions\ConnectionFailedException
189
     * @throws InvalidMessageDateException
190
     * @throws Exceptions\RuntimeException
191
     * @throws MessageHeaderFetchingException
192
     * @throws MessageContentFetchingException
193
     * @throws Exceptions\EventNotFoundException
194
     */
195
    public function __construct($msgn, $msglist, Client $client, $fetch_options = null, $fetch_body = false, $fetch_flags = false) {
196
197
        $default_mask = $client->getDefaultMessageMask();
198
        if($default_mask != null) {
199
            $this->mask = $default_mask;
200
        }
201
        $this->events["message"] = $client->getDefaultEvents("message");
202
        $this->events["flag"] = $client->getDefaultEvents("flag");
203
204
        $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...
205
206
        $this->config = ClientManager::get('options');
207
208
        $this->setFetchOption($fetch_options);
209
        $this->setFetchBodyOption($fetch_body);
210
        $this->setFetchFlagsOption($fetch_flags);
211
212
        $this->attachments = AttachmentCollection::make([]);
213
        $this->flags = FlagCollection::make([]);
214
215
        $this->client = $client;
216
        $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

216
        $this->client->openFolder(/** @scrutinizer ignore-type */ $this->folder_path);
Loading history...
217
218
        $this->msgn = $msgn;
219
        $this->msglist = $msglist;
220
221
        $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

221
        $this->uid = $this->client->getConnection()->/** @scrutinizer ignore-call */ getUid($this->msgn);
Loading history...
222
223
        $this->parseHeader();
224
225
        if ($this->getFetchBodyOption() === true) {
226
            $this->parseBody();
227
        }
228
229
        if ($this->getFetchFlagsOption() === true) {
230
            $this->parseFlags();
231
        }
232
    }
233
234
    /**
235
     * Create a new instance without fetching the message header and providing them raw instead
236
     * @param int $msgn
237
     * @param int|null $msglist
238
     * @param Client $client
239
     * @param string $raw_header
240
     * @param string $raw_body
241
     * @param string $raw_flags
242
     * @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...
243
     *
244
     * @return Message
245
     * @throws Exceptions\ConnectionFailedException
246
     * @throws Exceptions\EventNotFoundException
247
     * @throws Exceptions\RuntimeException
248
     * @throws InvalidMessageDateException
249
     * @throws MessageContentFetchingException
250
     * @throws \ReflectionException
251
     */
252
    public static function make($msgn, $msglist, Client $client, $raw_header, $raw_body, $raw_flags, $fetch_options = null){
253
        $reflection = new \ReflectionClass(self::class);
254
        /** @var self $instance */
255
        $instance = $reflection->newInstanceWithoutConstructor();
256
257
        $default_mask = $client->getDefaultMessageMask();
258
        if($default_mask != null) {
259
            $instance->setMask($default_mask);
260
        }
261
        $instance->setEvents([
262
            "message" => $client->getDefaultEvents("message"),
263
            "flag" => $client->getDefaultEvents("flag"),
264
        ]);
265
        $instance->setFolderPath($client->getFolderPath());
266
        $instance->setConfig(ClientManager::get('options'));
267
        $instance->setFetchOption($fetch_options);
268
269
        $instance->setAttachments(AttachmentCollection::make([]));
270
271
        $instance->setClient($client);
272
        $instance->setMsgn($msgn, $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

272
        $instance->setMsgn($msgn, /** @scrutinizer ignore-type */ $msglist);
Loading history...
273
274
        $instance->parseRawHeader($raw_header);
275
        $instance->parseRawFlags($raw_flags);
276
        $instance->parseRawBody($raw_body);
277
278
        return $instance;
279
    }
280
281
    /**
282
     * Call dynamic attribute setter and getter methods
283
     * @param string $method
284
     * @param array $arguments
285
     *
286
     * @return mixed
287
     * @throws MethodNotFoundException
288
     */
289
    public function __call($method, $arguments) {
290
        if(strtolower(substr($method, 0, 3)) === 'get') {
291
            $name = Str::snake(substr($method, 3));
292
            return $this->get($name);
293
        }elseif (strtolower(substr($method, 0, 3)) === 'set') {
294
            $name = Str::snake(substr($method, 3));
295
296
            if(in_array($name, array_keys($this->attributes))) {
297
                $this->attributes[$name] = array_pop($arguments);
298
299
                return $this->attributes[$name];
300
            }
301
302
        }
303
304
        throw new MethodNotFoundException("Method ".self::class.'::'.$method.'() is not supported');
305
    }
306
307
    /**
308
     * Magic setter
309
     * @param $name
310
     * @param $value
311
     *
312
     * @return mixed
313
     */
314
    public function __set($name, $value) {
315
        $this->attributes[$name] = $value;
316
317
        return $this->attributes[$name];
318
    }
319
320
    /**
321
     * Magic getter
322
     * @param $name
323
     *
324
     * @return mixed|null
325
     */
326
    public function __get($name) {
327
        return $this->get($name);
328
    }
329
330
    /**
331
     * Get an available message or message header attribute
332
     * @param $name
333
     *
334
     * @return mixed|null
335
     */
336
    public function get($name) {
337
        if(isset($this->attributes[$name])) {
338
            return $this->attributes[$name];
339
        }
340
341
        return $this->header->get($name);
342
    }
343
344
    /**
345
     * Check if the Message has a text body
346
     *
347
     * @return bool
348
     */
349
    public function hasTextBody() {
350
        return isset($this->bodies['text']);
351
    }
352
353
    /**
354
     * Get the Message text body
355
     *
356
     * @return mixed
357
     */
358
    public function getTextBody() {
359
        if (!isset($this->bodies['text'])) {
360
            return false;
361
        }
362
363
        return $this->bodies['text'];
364
    }
365
366
    /**
367
     * Check if the Message has a html body
368
     *
369
     * @return bool
370
     */
371
    public function hasHTMLBody() {
372
        return isset($this->bodies['html']);
373
    }
374
375
    /**
376
     * Get the Message html body
377
     *
378
     * @return string|null
379
     */
380
    public function getHTMLBody() {
381
        if (!isset($this->bodies['html'])) {
382
            return null;
383
        }
384
385
        return $this->bodies['html'];
386
    }
387
388
    /**
389
     * Parse all defined headers
390
     *
391
     * @throws Exceptions\ConnectionFailedException
392
     * @throws Exceptions\RuntimeException
393
     * @throws InvalidMessageDateException
394
     * @throws MessageHeaderFetchingException
395
     */
396
    private function parseHeader() {
397
        $headers = $this->client->getConnection()->headers([$this->msgn]);
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

397
        $headers = $this->client->getConnection()->/** @scrutinizer ignore-call */ headers([$this->msgn]);
Loading history...
398
        if (!isset($headers[$this->msgn])) {
399
            throw new MessageHeaderFetchingException("no headers found", 0);
400
        }
401
402
        $this->parseRawHeader($headers[$this->msgn]);
403
    }
404
405
    /**
406
     * @param string $raw_header
407
     *
408
     * @throws InvalidMessageDateException
409
     */
410
    public function parseRawHeader($raw_header){
411
        $this->header = new Header($raw_header);
412
    }
413
414
    /**
415
     * Parse additional raw flags
416
     * @param $raw_flags
417
     */
418
    public function parseRawFlags($raw_flags) {
419
        $this->flags = FlagCollection::make([]);
420
421
        foreach($raw_flags as $flag) {
422
            if (strpos($flag, "\\") === 0){
423
                $flag = substr($flag, 1);
424
            }
425
            $flag_key = strtolower($flag);
426
            if (in_array($flag_key, $this->available_flags)) {
427
                $this->flags->put($flag_key, $flag);
428
            }
429
        }
430
    }
431
432
    /**
433
     * Parse additional flags
434
     *
435
     * @return void
436
     * @throws Exceptions\ConnectionFailedException
437
     * @throws Exceptions\RuntimeException
438
     */
439
    private function parseFlags() {
440
        $this->client->openFolder($this->folder_path);
441
        $this->flags = FlagCollection::make([]);
442
443
        $flags = $this->client->getConnection()->flags([$this->msgn]);
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

443
        $flags = $this->client->getConnection()->/** @scrutinizer ignore-call */ flags([$this->msgn]);
Loading history...
444
445
        if (isset($flags[$this->msgn])) {
446
            $this->parseRawFlags($flags[$this->msgn]);
447
        }
448
    }
449
450
    /**
451
     * Parse the Message body
452
     *
453
     * @return $this
454
     * @throws Exceptions\ConnectionFailedException
455
     * @throws Exceptions\MessageContentFetchingException
456
     * @throws InvalidMessageDateException
457
     * @throws Exceptions\RuntimeException
458
     * @throws Exceptions\EventNotFoundException
459
     */
460
    public function parseBody() {
461
        $this->client->openFolder($this->folder_path);
462
463
        if ($this->fetch_options == IMAP::FT_PEEK && $this->flags->count() == 0) {
464
            $this->parseFlags();
465
        }
466
467
        $contents = $this->client->getConnection()->content([$this->msgn]);
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

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

792
        $status = $this->client->getConnection()->/** @scrutinizer ignore-call */ examineFolder($folder_path);
Loading history...
793
794
        if (isset($status["uidnext"])) {
795
            $next_uid = $status["uidnext"];
796
797
            /** @var Folder $folder */
798
            $folder = $this->client->getFolder($folder_path);
799
800
            $this->client->openFolder($this->folder_path);
801
            if ($this->client->getConnection()->copyMessage($folder->path, $this->msgn) == 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

801
            if ($this->client->getConnection()->/** @scrutinizer ignore-call */ copyMessage($folder->path, $this->msgn) == true) {
Loading history...
802
                if($expunge) $this->client->expunge();
803
804
                $this->client->openFolder($folder->path);
805
                $message_num = $this->client->getConnection()->getMessageNumber($next_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

805
                $message_num = $this->client->getConnection()->/** @scrutinizer ignore-call */ getMessageNumber($next_uid);
Loading history...
806
807
                $message = $folder->query()->getMessage($message_num);
808
                $event = $this->getEvent("message", "copied");
809
                $event::dispatch($this, $message);
810
811
                return $message;
812
            }
813
        }
814
815
        return null;
816
    }
817
818
    /**
819
     * Move the current Messages to a mailbox
820
     * @param string $folder_path
821
     * @param boolean $expunge
822
     *
823
     * @return Message|null
824
     * @throws Exceptions\ConnectionFailedException
825
     * @throws Exceptions\FolderFetchingException
826
     * @throws Exceptions\RuntimeException
827
     * @throws InvalidMessageDateException
828
     * @throws MessageContentFetchingException
829
     * @throws MessageHeaderFetchingException
830
     * @throws Exceptions\EventNotFoundException
831
     */
832
    public function move($folder_path, $expunge = false) {
833
        $this->client->openFolder($folder_path);
834
        $status = $this->client->getConnection()->examineFolder($folder_path);
835
836
        if (isset($status["uidnext"])) {
837
            $next_uid = $status["uidnext"];
838
839
            /** @var Folder $folder */
840
            $folder = $this->client->getFolder($folder_path);
841
842
            $this->client->openFolder($this->folder_path);
843
            if ($this->client->getConnection()->moveMessage($folder->path, $this->msgn) == 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

843
            if ($this->client->getConnection()->/** @scrutinizer ignore-call */ moveMessage($folder->path, $this->msgn) == true) {
Loading history...
844
                if($expunge) $this->client->expunge();
845
846
                $this->client->openFolder($folder->path);
847
                $message_num = $this->client->getConnection()->getMessageNumber($next_uid);
848
849
                $message = $folder->query()->getMessage($message_num);
850
                $event = $this->getEvent("message", "moved");
851
                $event::dispatch($this, $message);
852
853
                return $message;
854
            }
855
        }
856
857
        return null;
858
    }
859
860
    /**
861
     * Delete the current Message
862
     * @param bool $expunge
863
     *
864
     * @return bool
865
     * @throws Exceptions\ConnectionFailedException
866
     * @throws Exceptions\RuntimeException
867
     * @throws Exceptions\EventNotFoundException
868
     */
869
    public function delete($expunge = true) {
870
        $status = $this->setFlag("Deleted");
871
        if($expunge) $this->client->expunge();
872
873
        $event = $this->getEvent("message", "deleted");
874
        $event::dispatch($this);
875
876
        return $status;
877
    }
878
879
    /**
880
     * Restore a deleted Message
881
     * @param boolean $expunge
882
     *
883
     * @return bool
884
     * @throws Exceptions\ConnectionFailedException
885
     * @throws Exceptions\RuntimeException
886
     * @throws Exceptions\EventNotFoundException
887
     */
888
    public function restore($expunge = true) {
889
        $status = $this->unsetFlag("Deleted");
890
        if($expunge) $this->client->expunge();
891
892
        $event = $this->getEvent("message", "restored");
893
        $event::dispatch($this);
894
895
        return $status;
896
    }
897
898
    /**
899
     * Set a given flag
900
     * @param string|array $flag
901
     *
902
     * @return bool
903
     * @throws Exceptions\ConnectionFailedException
904
     * @throws Exceptions\RuntimeException
905
     * @throws Exceptions\EventNotFoundException
906
     */
907
    public function setFlag($flag) {
908
        $this->client->openFolder($this->folder_path);
909
        $flag = "\\".trim(is_array($flag) ? implode(" \\", $flag) : $flag);
910
        $status = $this->client->getConnection()->store([$flag], $this->msgn, $this->msgn, "+");
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

910
        $status = $this->client->getConnection()->/** @scrutinizer ignore-call */ store([$flag], $this->msgn, $this->msgn, "+");
Loading history...
911
        $this->parseFlags();
912
913
        $event = $this->getEvent("flag", "new");
914
        $event::dispatch($this, $flag);
915
916
        return $status;
917
    }
918
919
    /**
920
     * Unset a given flag
921
     * @param string|array $flag
922
     *
923
     * @return bool
924
     * @throws Exceptions\ConnectionFailedException
925
     * @throws Exceptions\RuntimeException
926
     * @throws Exceptions\EventNotFoundException
927
     */
928
    public function unsetFlag($flag) {
929
        $this->client->openFolder($this->folder_path);
930
931
        $flag = "\\".trim(is_array($flag) ? implode(" \\", $flag) : $flag);
932
        $status = $this->client->getConnection()->store([$flag], $this->msgn, $this->msgn, "-");
933
        $this->parseFlags();
934
935
        $event = $this->getEvent("flag", "deleted");
936
        $event::dispatch($this, $flag);
937
938
        return $status;
939
    }
940
941
    /**
942
     * Get all message attachments.
943
     *
944
     * @return AttachmentCollection
945
     */
946
    public function getAttachments() {
947
        return $this->attachments;
948
    }
949
950
    /**
951
     * Checks if there are any attachments present
952
     *
953
     * @return boolean
954
     */
955
    public function hasAttachments() {
956
        return $this->attachments->isEmpty() === false;
957
    }
958
959
    /**
960
     * Get the raw body
961
     *
962
     * @return string
963
     * @throws Exceptions\ConnectionFailedException
964
     */
965
    public function getRawBody() {
966
        if ($this->raw_body === null) {
967
            $this->client->openFolder($this->folder_path);
968
969
            $this->raw_body = $this->structure->raw;
970
        }
971
972
        return $this->raw_body;
973
    }
974
975
    /**
976
     * Get the message header
977
     *
978
     * @return Header
979
     */
980
    public function getHeader() {
981
        return $this->header;
982
    }
983
984
    /**
985
     * Get the current client
986
     *
987
     * @return Client
988
     */
989
    public function getClient() {
990
        return $this->client;
991
    }
992
993
    /**
994
     * Get the used fetch option
995
     *
996
     * @return integer
997
     */
998
    public function getFetchOptions() {
999
        return $this->fetch_options;
1000
    }
1001
1002
    /**
1003
     * Get the used fetch body option
1004
     *
1005
     * @return boolean
1006
     */
1007
    public function getFetchBodyOption() {
1008
        return $this->fetch_body;
1009
    }
1010
1011
    /**
1012
     * Get the used fetch flags option
1013
     *
1014
     * @return boolean
1015
     */
1016
    public function getFetchFlagsOption() {
1017
        return $this->fetch_flags;
1018
    }
1019
1020
    /**
1021
     * Get all available bodies
1022
     *
1023
     * @return array
1024
     */
1025
    public function getBodies() {
1026
        return $this->bodies;
1027
    }
1028
1029
    /**
1030
     * Get all set flags
1031
     *
1032
     * @return FlagCollection
1033
     */
1034
    public function getFlags() {
1035
        return $this->flags;
1036
    }
1037
1038
    /**
1039
     * Get the fetched structure
1040
     *
1041
     * @return object|null
1042
     */
1043
    public function getStructure(){
1044
        return $this->structure;
1045
    }
1046
1047
    /**
1048
     * Check if a message matches an other by comparing basic attributes
1049
     *
1050
     * @param  null|Message $message
1051
     * @return boolean
1052
     */
1053
    public function is(Message $message = null) {
1054
        if (is_null($message)) {
1055
            return false;
1056
        }
1057
1058
        return $this->uid == $message->uid
1059
            && $this->message_id == $message->message_id
1060
            && $this->subject == $message->subject
1061
            && $this->date->eq($message->date);
1062
    }
1063
1064
    /**
1065
     * Get all message attributes
1066
     *
1067
     * @return array
1068
     */
1069
    public function getAttributes(){
1070
        return array_merge($this->attributes, $this->header->getAttributes());
1071
    }
1072
1073
    /**
1074
     * Set the message mask
1075
     * @param $mask
1076
     *
1077
     * @return $this
1078
     */
1079
    public function setMask($mask){
1080
        if(class_exists($mask)){
1081
            $this->mask = $mask;
1082
        }
1083
1084
        return $this;
1085
    }
1086
1087
    /**
1088
     * Get the used message mask
1089
     *
1090
     * @return string
1091
     */
1092
    public function getMask(){
1093
        return $this->mask;
1094
    }
1095
1096
    /**
1097
     * Get a masked instance by providing a mask name
1098
     * @param string|null $mask
1099
     *
1100
     * @return mixed
1101
     * @throws MaskNotFoundException
1102
     */
1103
    public function mask($mask = null){
1104
        $mask = $mask !== null ? $mask : $this->mask;
1105
        if(class_exists($mask)){
1106
            return new $mask($this);
1107
        }
1108
1109
        throw new MaskNotFoundException("Unknown mask provided: ".$mask);
1110
    }
1111
1112
    /**
1113
     * Set the message path aka folder path
1114
     * @param $folder_path
1115
     *
1116
     * @return $this
1117
     */
1118
    public function setFolderPath($folder_path){
1119
        $this->folder_path = $folder_path;
1120
1121
        return $this;
1122
    }
1123
1124
    /**
1125
     * Set the config
1126
     * @param $config
1127
     *
1128
     * @return $this
1129
     */
1130
    public function setConfig($config){
1131
        $this->config = $config;
1132
1133
        return $this;
1134
    }
1135
1136
    /**
1137
     * Set the attachment collection
1138
     * @param $attachments
1139
     *
1140
     * @return $this
1141
     */
1142
    public function setAttachments($attachments){
1143
        $this->attachments = $attachments;
1144
1145
        return $this;
1146
    }
1147
1148
    /**
1149
     * Set the flag collection
1150
     * @param $flags
1151
     *
1152
     * @return $this
1153
     */
1154
    public function setFlags($flags){
1155
        $this->flags = $flags;
1156
1157
        return $this;
1158
    }
1159
1160
    /**
1161
     * Set the client
1162
     * @param $client
1163
     *
1164
     * @throws Exceptions\ConnectionFailedException
1165
     * @return $this
1166
     */
1167
    public function setClient($client){
1168
        $this->client = $client;
1169
        $this->client->openFolder($this->folder_path);
1170
1171
        return $this;
1172
    }
1173
1174
    /**
1175
     * Set the message number
1176
     * @param $msgn
1177
     * @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...
1178
     *
1179
     * @throws Exceptions\ConnectionFailedException
1180
     * @throws Exceptions\RuntimeException
1181
     * @return $this
1182
     */
1183
    public function setMsgn($msgn, $msglist = null){
1184
        $this->msgn = $msgn;
1185
        $this->msglist = $msglist;
1186
1187
        $this->uid = $this->client->getConnection()->getUid($this->msgn);
1188
1189
        return $this;
1190
    }
1191
}
1192