Passed
Push — master ( 9b186e...d00b04 )
by Malte
01:55
created

Message::make()   B

Complexity

Conditions 7
Paths 16

Size

Total Lines 41
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 25
c 0
b 0
f 0
dl 0
loc 41
rs 8.5866
cc 7
nc 16
nop 7
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
277
278
        if ($fetch_options == IMAP::FT_PEEK && $instance->getFlags()->count() == 0) {
279
            $instance->parseFlags();
280
        }
281
282
        $instance->parseRawBody($raw_body);
283
284
        if ($fetch_options == IMAP::FT_PEEK) {
285
            if ($instance->getFlags()->get("seen") == null) {
286
                $instance->unsetFlag("Seen");
287
            }
288
        } elseif ($instance->getFlags()->get("seen") == null) {
289
            $instance->setFlag("Seen");
290
        }
291
292
        return $instance;
293
    }
294
295
    /**
296
     * Call dynamic attribute setter and getter methods
297
     * @param string $method
298
     * @param array $arguments
299
     *
300
     * @return mixed
301
     * @throws MethodNotFoundException
302
     */
303
    public function __call($method, $arguments) {
304
        if(strtolower(substr($method, 0, 3)) === 'get') {
305
            $name = Str::snake(substr($method, 3));
306
            return $this->get($name);
307
        }elseif (strtolower(substr($method, 0, 3)) === 'set') {
308
            $name = Str::snake(substr($method, 3));
309
310
            if(in_array($name, array_keys($this->attributes))) {
311
                $this->attributes[$name] = array_pop($arguments);
312
313
                return $this->attributes[$name];
314
            }
315
316
        }
317
318
        throw new MethodNotFoundException("Method ".self::class.'::'.$method.'() is not supported');
319
    }
320
321
    /**
322
     * Magic setter
323
     * @param $name
324
     * @param $value
325
     *
326
     * @return mixed
327
     */
328
    public function __set($name, $value) {
329
        $this->attributes[$name] = $value;
330
331
        return $this->attributes[$name];
332
    }
333
334
    /**
335
     * Magic getter
336
     * @param $name
337
     *
338
     * @return mixed|null
339
     */
340
    public function __get($name) {
341
        return $this->get($name);
342
    }
343
344
    /**
345
     * Get an available message or message header attribute
346
     * @param $name
347
     *
348
     * @return mixed|null
349
     */
350
    public function get($name) {
351
        if(isset($this->attributes[$name])) {
352
            return $this->attributes[$name];
353
        }
354
355
        return $this->header->get($name);
356
    }
357
358
    /**
359
     * Check if the Message has a text body
360
     *
361
     * @return bool
362
     */
363
    public function hasTextBody() {
364
        return isset($this->bodies['text']);
365
    }
366
367
    /**
368
     * Get the Message text body
369
     *
370
     * @return mixed
371
     */
372
    public function getTextBody() {
373
        if (!isset($this->bodies['text'])) {
374
            return false;
375
        }
376
377
        return $this->bodies['text'];
378
    }
379
380
    /**
381
     * Check if the Message has a html body
382
     *
383
     * @return bool
384
     */
385
    public function hasHTMLBody() {
386
        return isset($this->bodies['html']);
387
    }
388
389
    /**
390
     * Get the Message html body
391
     *
392
     * @return string|null
393
     */
394
    public function getHTMLBody() {
395
        if (!isset($this->bodies['html'])) {
396
            return null;
397
        }
398
399
        return $this->bodies['html'];
400
    }
401
402
    /**
403
     * Parse all defined headers
404
     *
405
     * @throws Exceptions\ConnectionFailedException
406
     * @throws Exceptions\RuntimeException
407
     * @throws InvalidMessageDateException
408
     * @throws MessageHeaderFetchingException
409
     */
410
    private function parseHeader() {
411
        $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

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

457
        $flags = $this->client->getConnection()->/** @scrutinizer ignore-call */ flags([$this->msgn]);
Loading history...
458
459
        if (isset($flags[$this->msgn])) {
460
            $this->parseRawFlags($flags[$this->msgn]);
461
        }
462
    }
463
464
    /**
465
     * Parse the Message body
466
     *
467
     * @return $this
468
     * @throws Exceptions\ConnectionFailedException
469
     * @throws Exceptions\MessageContentFetchingException
470
     * @throws InvalidMessageDateException
471
     * @throws Exceptions\RuntimeException
472
     * @throws Exceptions\EventNotFoundException
473
     */
474
    public function parseBody() {
475
        $this->client->openFolder($this->folder_path);
476
477
        if ($this->fetch_options == IMAP::FT_PEEK && $this->flags->count() == 0) {
478
            $this->parseFlags();
479
        }
480
481
        $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

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

808
        $status = $this->client->getConnection()->/** @scrutinizer ignore-call */ examineFolder($folder_path);
Loading history...
809
810
        if (isset($status["uidnext"])) {
811
            $next_uid = $status["uidnext"];
812
813
            /** @var Folder $folder */
814
            $folder = $this->client->getFolder($folder_path);
815
816
            $this->client->openFolder($this->folder_path);
817
            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

817
            if ($this->client->getConnection()->/** @scrutinizer ignore-call */ copyMessage($folder->path, $this->msgn) == true) {
Loading history...
818
                if($expunge) $this->client->expunge();
819
820
                $this->client->openFolder($folder->path);
821
                $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

821
                $message_num = $this->client->getConnection()->/** @scrutinizer ignore-call */ getMessageNumber($next_uid);
Loading history...
822
823
                $message = $folder->query()->getMessage($message_num);
824
                $event = $this->getEvent("message", "copied");
825
                $event::dispatch($this, $message);
826
827
                return $message;
828
            }
829
        }
830
831
        return null;
832
    }
833
834
    /**
835
     * Move the current Messages to a mailbox
836
     * @param string $folder_path
837
     * @param boolean $expunge
838
     *
839
     * @return Message|null
840
     * @throws Exceptions\ConnectionFailedException
841
     * @throws Exceptions\FolderFetchingException
842
     * @throws Exceptions\RuntimeException
843
     * @throws InvalidMessageDateException
844
     * @throws MessageContentFetchingException
845
     * @throws MessageHeaderFetchingException
846
     * @throws Exceptions\EventNotFoundException
847
     */
848
    public function move($folder_path, $expunge = false) {
849
        $this->client->openFolder($folder_path);
850
        $status = $this->client->getConnection()->examineFolder($folder_path);
851
852
        if (isset($status["uidnext"])) {
853
            $next_uid = $status["uidnext"];
854
855
            /** @var Folder $folder */
856
            $folder = $this->client->getFolder($folder_path);
857
858
            $this->client->openFolder($this->folder_path);
859
            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

859
            if ($this->client->getConnection()->/** @scrutinizer ignore-call */ moveMessage($folder->path, $this->msgn) == true) {
Loading history...
860
                if($expunge) $this->client->expunge();
861
862
                $this->client->openFolder($folder->path);
863
                $message_num = $this->client->getConnection()->getMessageNumber($next_uid);
864
865
                $message = $folder->query()->getMessage($message_num);
866
                $event = $this->getEvent("message", "moved");
867
                $event::dispatch($this, $message);
868
869
                return $message;
870
            }
871
        }
872
873
        return null;
874
    }
875
876
    /**
877
     * Delete the current Message
878
     * @param bool $expunge
879
     *
880
     * @return bool
881
     * @throws Exceptions\ConnectionFailedException
882
     * @throws Exceptions\RuntimeException
883
     * @throws Exceptions\EventNotFoundException
884
     */
885
    public function delete($expunge = true) {
886
        $status = $this->setFlag("Deleted");
887
        if($expunge) $this->client->expunge();
888
889
        $event = $this->getEvent("message", "deleted");
890
        $event::dispatch($this);
891
892
        return $status;
893
    }
894
895
    /**
896
     * Restore a deleted Message
897
     * @param boolean $expunge
898
     *
899
     * @return bool
900
     * @throws Exceptions\ConnectionFailedException
901
     * @throws Exceptions\RuntimeException
902
     * @throws Exceptions\EventNotFoundException
903
     */
904
    public function restore($expunge = true) {
905
        $status = $this->unsetFlag("Deleted");
906
        if($expunge) $this->client->expunge();
907
908
        $event = $this->getEvent("message", "restored");
909
        $event::dispatch($this);
910
911
        return $status;
912
    }
913
914
    /**
915
     * Set a given flag
916
     * @param string|array $flag
917
     *
918
     * @return bool
919
     * @throws Exceptions\ConnectionFailedException
920
     * @throws Exceptions\RuntimeException
921
     * @throws Exceptions\EventNotFoundException
922
     */
923
    public function setFlag($flag) {
924
        $this->client->openFolder($this->folder_path);
925
        $flag = "\\".trim(is_array($flag) ? implode(" \\", $flag) : $flag);
926
        $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

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