Passed
Push — master ( 4b5f38...6aa44d )
by Malte
02:26
created

Message::getStructure()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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

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

222
        $this->uid = $this->client->getConnection()->/** @scrutinizer ignore-call */ getUid($this->msgn);
Loading history...
223
224
        $this->parseHeader();
225
226
        if ($this->getFetchBodyOption() === true) {
227
            $this->parseBody();
228
        }
229
230
        if ($this->getFetchFlagsOption() === true) {
231
            $this->parseFlags();
232
        }
233
    }
234
235
    /**
236
     * Call dynamic attribute setter and getter methods
237
     * @param string $method
238
     * @param array $arguments
239
     *
240
     * @return mixed
241
     * @throws MethodNotFoundException
242
     */
243
    public function __call($method, $arguments) {
244
        if(strtolower(substr($method, 0, 3)) === 'get') {
245
            $name = Str::snake(substr($method, 3));
246
            return $this->get($name);
247
        }elseif (strtolower(substr($method, 0, 3)) === 'set') {
248
            $name = Str::snake(substr($method, 3));
249
250
            if(in_array($name, array_keys($this->attributes))) {
251
                $this->attributes[$name] = array_pop($arguments);
252
253
                return $this->attributes[$name];
254
            }
255
256
        }
257
258
        throw new MethodNotFoundException("Method ".self::class.'::'.$method.'() is not supported');
259
    }
260
261
    /**
262
     * Magic setter
263
     * @param $name
264
     * @param $value
265
     *
266
     * @return mixed
267
     */
268
    public function __set($name, $value) {
269
        $this->attributes[$name] = $value;
270
271
        return $this->attributes[$name];
272
    }
273
274
    /**
275
     * Magic getter
276
     * @param $name
277
     *
278
     * @return mixed|null
279
     */
280
    public function __get($name) {
281
        return $this->get($name);
282
    }
283
284
    /**
285
     * Get an available message or message header attribute
286
     * @param $name
287
     *
288
     * @return mixed|null
289
     */
290
    public function get($name) {
291
        if(isset($this->attributes[$name])) {
292
            return $this->attributes[$name];
293
        }
294
295
        return $this->header->get($name);
296
    }
297
298
    /**
299
     * Check if the Message has a text body
300
     *
301
     * @return bool
302
     */
303
    public function hasTextBody() {
304
        return isset($this->bodies['text']);
305
    }
306
307
    /**
308
     * Get the Message text body
309
     *
310
     * @return mixed
311
     */
312
    public function getTextBody() {
313
        if (!isset($this->bodies['text'])) {
314
            return false;
315
        }
316
317
        return $this->bodies['text'];
318
    }
319
320
    /**
321
     * Check if the Message has a html body
322
     *
323
     * @return bool
324
     */
325
    public function hasHTMLBody() {
326
        return isset($this->bodies['html']);
327
    }
328
329
    /**
330
     * Get the Message html body
331
     *
332
     * @return string|null
333
     */
334
    public function getHTMLBody() {
335
        if (!isset($this->bodies['html'])) {
336
            return null;
337
        }
338
339
        return $this->bodies['html'];
340
    }
341
342
    /**
343
     * Parse all defined headers
344
     *
345
     * @throws Exceptions\ConnectionFailedException
346
     * @throws Exceptions\RuntimeException
347
     * @throws InvalidMessageDateException
348
     * @throws MessageHeaderFetchingException
349
     */
350
    private function parseHeader() {
351
        $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

351
        $headers = $this->client->getConnection()->/** @scrutinizer ignore-call */ headers([$this->msgn]);
Loading history...
352
        if (!isset($headers[$this->msgn])) {
353
            throw new MessageHeaderFetchingException("no headers found", 0);
354
        }
355
356
        $this->header = new Header($headers[$this->msgn]);
357
    }
358
359
    /**
360
     * Parse additional flags
361
     *
362
     * @return void
363
     * @throws Exceptions\ConnectionFailedException
364
     * @throws Exceptions\RuntimeException
365
     */
366
    private function parseFlags() {
367
        $this->client->openFolder($this->folder_path);
368
        $this->flags = FlagCollection::make([]);
369
370
        $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

370
        $flags = $this->client->getConnection()->/** @scrutinizer ignore-call */ flags([$this->msgn]);
Loading history...
371
372
        if (isset($flags[$this->msgn])) {
373
            foreach($flags[$this->msgn] as $flag) {
374
                if (strpos($flag, "\\") === 0){
375
                    $flag = substr($flag, 1);
376
                }
377
                $flag_key = strtolower($flag);
378
                if (in_array($flag_key, $this->available_flags)) {
379
                    $this->flags->put($flag_key, $flag);
380
                }
381
            }
382
        }
383
    }
384
385
    /**
386
     * Parse the Message body
387
     *
388
     * @return $this
389
     * @throws Exceptions\ConnectionFailedException
390
     * @throws Exceptions\MessageContentFetchingException
391
     * @throws InvalidMessageDateException
392
     * @throws Exceptions\RuntimeException
393
     * @throws Exceptions\EventNotFoundException
394
     */
395
    public function parseBody() {
396
        $this->client->openFolder($this->folder_path);
397
398
        if ($this->fetch_options == IMAP::FT_PEEK && $this->flags->count() == 0) {
399
            $this->parseFlags();
400
        }
401
402
        $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

402
        $contents = $this->client->getConnection()->/** @scrutinizer ignore-call */ content([$this->msgn]);
Loading history...
403
        if (!isset($contents[$this->msgn])) {
404
            throw new MessageContentFetchingException("no content found", 0);
405
        }
406
        $content = $contents[$this->msgn];
407
408
        $this->structure = new Structure($content, $this->header);
409
410
        $this->fetchStructure($this->structure);
411
412
        if ($this->fetch_options == IMAP::FT_PEEK) {
413
            if ($this->getFlags()->get("seen") == null) {
414
                $this->unsetFlag("Seen");
415
            }
416
        }
417
418
        return $this;
419
    }
420
421
    /**
422
     * Fetch the Message structure
423
     * @param $structure
424
     *
425
     * @throws Exceptions\ConnectionFailedException
426
     */
427
    private function fetchStructure($structure) {
428
        $this->client->openFolder($this->folder_path);
429
430
        foreach ($structure->parts as $part) {
431
            $this->fetchPart($part);
432
        }
433
    }
434
435
    /**
436
     * Fetch a given part
437
     * @param Part $part
438
     */
439
    private function fetchPart(Part $part) {
440
441
        if ($part->isAttachment()) {
442
            $this->fetchAttachment($part);
443
        }else{
444
            $encoding = $this->getEncoding($part);
445
446
            $content = $this->decodeString($part->content, $part->encoding);
447
448
            // We don't need to do convertEncoding() if charset is ASCII (us-ascii):
449
            //     ASCII is a subset of UTF-8, so all ASCII files are already UTF-8 encoded
450
            //     https://stackoverflow.com/a/11303410
451
            //
452
            // us-ascii is the same as ASCII:
453
            //     ASCII is the traditional name for the encoding system; the Internet Assigned Numbers Authority (IANA)
454
            //     prefers the updated name US-ASCII, which clarifies that this system was developed in the US and
455
            //     based on the typographical symbols predominantly in use there.
456
            //     https://en.wikipedia.org/wiki/ASCII
457
            //
458
            // convertEncoding() function basically means convertToUtf8(), so when we convert ASCII string into UTF-8 it gets broken.
459
            if ($encoding != 'us-ascii') {
460
                $content = $this->convertEncoding($content, $encoding);
461
            }
462
463
            $subtype = strtolower($part->subtype);
464
            $subtype = $subtype == "plain" || $subtype == "" ? "text" : $subtype;
465
466
            $this->bodies[$subtype] = $content;
467
        }
468
    }
469
470
    /**
471
     * Fetch the Message attachment
472
     * @param Part $part
473
     */
474
    protected function fetchAttachment($part) {
475
476
        $oAttachment = new Attachment($this, $part);
477
478
        if ($oAttachment->getName() !== null && $oAttachment->getSize() > 0) {
479
            if ($oAttachment->getId() !== null) {
0 ignored issues
show
introduced by
The condition $oAttachment->getId() !== null is always true.
Loading history...
480
                $this->attachments->put($oAttachment->getId(), $oAttachment);
481
            } else {
482
                $this->attachments->push($oAttachment);
483
            }
484
        }
485
    }
486
487
    /**
488
     * Fail proof setter for $fetch_option
489
     * @param $option
490
     *
491
     * @return $this
492
     */
493
    public function setFetchOption($option) {
494
        if (is_long($option) === true) {
495
            $this->fetch_options = $option;
496
        } elseif (is_null($option) === true) {
497
            $config = ClientManager::get('options.fetch', IMAP::FT_UID);
498
            $this->fetch_options = is_long($config) ? $config : 1;
499
        }
500
501
        return $this;
502
    }
503
504
    /**
505
     * Fail proof setter for $fetch_body
506
     * @param $option
507
     *
508
     * @return $this
509
     */
510
    public function setFetchBodyOption($option) {
511
        if (is_bool($option)) {
512
            $this->fetch_body = $option;
513
        } elseif (is_null($option)) {
514
            $config = ClientManager::get('options.fetch_body', true);
515
            $this->fetch_body = is_bool($config) ? $config : true;
516
        }
517
518
        return $this;
519
    }
520
521
    /**
522
     * Fail proof setter for $fetch_flags
523
     * @param $option
524
     *
525
     * @return $this
526
     */
527
    public function setFetchFlagsOption($option) {
528
        if (is_bool($option)) {
529
            $this->fetch_flags = $option;
530
        } elseif (is_null($option)) {
531
            $config = ClientManager::get('options.fetch_flags', true);
532
            $this->fetch_flags = is_bool($config) ? $config : true;
533
        }
534
535
        return $this;
536
    }
537
538
    /**
539
     * Decode a given string
540
     * @param $string
541
     * @param $encoding
542
     *
543
     * @return string
544
     */
545
    public function decodeString($string, $encoding) {
546
        switch ($encoding) {
547
            case IMAP::MESSAGE_ENC_BINARY:
548
                if (extension_loaded('imap')) {
549
                    return base64_decode(\imap_binary($string));
550
                }
551
                return base64_decode($string);
552
            case IMAP::MESSAGE_ENC_BASE64:
553
                return base64_decode($string);
554
            case IMAP::MESSAGE_ENC_8BIT:
555
            case IMAP::MESSAGE_ENC_QUOTED_PRINTABLE:
556
                return quoted_printable_decode($string);
557
            case IMAP::MESSAGE_ENC_7BIT:
558
            case IMAP::MESSAGE_ENC_OTHER:
559
            default:
560
                return $string;
561
        }
562
    }
563
564
    /**
565
     * Convert the encoding
566
     * @param $str
567
     * @param string $from
568
     * @param string $to
569
     *
570
     * @return mixed|string
571
     */
572
    public function convertEncoding($str, $from = "ISO-8859-2", $to = "UTF-8") {
573
574
        $from = EncodingAliases::get($from);
575
        $to = EncodingAliases::get($to);
576
577
        if ($from === $to) {
578
            return $str;
579
        }
580
581
        // We don't need to do convertEncoding() if charset is ASCII (us-ascii):
582
        //     ASCII is a subset of UTF-8, so all ASCII files are already UTF-8 encoded
583
        //     https://stackoverflow.com/a/11303410
584
        //
585
        // us-ascii is the same as ASCII:
586
        //     ASCII is the traditional name for the encoding system; the Internet Assigned Numbers Authority (IANA)
587
        //     prefers the updated name US-ASCII, which clarifies that this system was developed in the US and
588
        //     based on the typographical symbols predominantly in use there.
589
        //     https://en.wikipedia.org/wiki/ASCII
590
        //
591
        // convertEncoding() function basically means convertToUtf8(), so when we convert ASCII string into UTF-8 it gets broken.
592
        if (strtolower($from) == 'us-ascii' && $to == 'UTF-8') {
593
            return $str;
594
        }
595
596
        if (function_exists('iconv') && $from != 'UTF-7' && $to != 'UTF-7') {
597
            return @iconv($from, $to.'//IGNORE', $str);
598
        } else {
599
            if (!$from) {
600
                return mb_convert_encoding($str, $to);
601
            }
602
            return mb_convert_encoding($str, $to, $from);
603
        }
604
    }
605
606
    /**
607
     * Get the encoding of a given abject
608
     * @param object|string $structure
609
     *
610
     * @return string
611
     */
612
    public function getEncoding($structure) {
613
        if (property_exists($structure, 'parameters')) {
614
            foreach ($structure->parameters as $parameter) {
615
                if (strtolower($parameter->attribute) == "charset") {
616
                    return EncodingAliases::get($parameter->value);
617
                }
618
            }
619
        }elseif (property_exists($structure, 'charset')){
620
            return EncodingAliases::get($structure->charset);
621
        }elseif (is_string($structure) === true){
622
            return mb_detect_encoding($structure);
623
        }
624
625
        return 'UTF-8';
626
    }
627
628
    /**
629
     * Get the messages folder
630
     *
631
     * @return mixed
632
     * @throws Exceptions\ConnectionFailedException
633
     * @throws Exceptions\FolderFetchingException
634
     */
635
    public function getFolder(){
636
        return $this->client->getFolder($this->folder_path);
637
    }
638
639
    /**
640
     * Create a message thread based on the current message
641
     * @param Folder|null $sent_folder
642
     * @param MessageCollection|null $thread
643
     * @param Folder|null $folder
644
     *
645
     * @return MessageCollection|null
646
     * @throws Exceptions\ConnectionFailedException
647
     * @throws Exceptions\FolderFetchingException
648
     * @throws Exceptions\GetMessagesFailedException
649
     */
650
    public function thread($sent_folder = null, &$thread = null, $folder = null){
651
        $thread = $thread ? $thread : MessageCollection::make([]);
652
        $folder = $folder ? $folder :  $this->getFolder();
653
        $sent_folder = $sent_folder ? $sent_folder : $this->client->getFolder(ClientManager::get("options.common_folders.sent", "INBOX/Sent"));
654
655
        /** @var Message $message */
656
        foreach($thread as $message) {
657
            if ($message->message_id == $this->message_id) {
658
                return $thread;
659
            }
660
        }
661
        $thread->push($this);
662
663
        $folder->query()->inReplyTo($this->message_id)
664
            ->setFetchBody($this->getFetchBodyOption())
665
            ->leaveUnread()->get()->each(function($message) use(&$thread, $folder, $sent_folder){
666
            /** @var Message $message */
667
            $message->thread($sent_folder, $thread, $folder);
668
        });
669
        $sent_folder->query()->inReplyTo($this->message_id)
670
            ->setFetchBody($this->getFetchBodyOption())
671
            ->leaveUnread()->get()->each(function($message) use(&$thread, $folder, $sent_folder){
672
            /** @var Message $message */
673
                $message->thread($sent_folder, $thread, $folder);
674
        });
675
676
        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...
677
            foreach($this->in_reply_to as $in_reply_to) {
678
                $folder->query()->messageId($in_reply_to)
679
                    ->setFetchBody($this->getFetchBodyOption())
680
                    ->leaveUnread()->get()->each(function($message) use(&$thread, $folder, $sent_folder){
681
                    /** @var Message $message */
682
                        $message->thread($sent_folder, $thread, $folder);
683
                });
684
                $sent_folder->query()->messageId($in_reply_to)
685
                    ->setFetchBody($this->getFetchBodyOption())
686
                    ->leaveUnread()->get()->each(function($message) use(&$thread, $folder, $sent_folder){
687
                    /** @var Message $message */
688
                        $message->thread($sent_folder, $thread, $folder);
689
                });
690
            }
691
        }
692
693
        return $thread;
694
    }
695
696
    /**
697
     * Copy the current Messages to a mailbox
698
     * @param string $folder_path
699
     * @param boolean $expunge
700
     *
701
     * @return null|Message
702
     * @throws Exceptions\ConnectionFailedException
703
     * @throws Exceptions\FolderFetchingException
704
     * @throws Exceptions\RuntimeException
705
     * @throws InvalidMessageDateException
706
     * @throws MessageContentFetchingException
707
     * @throws MessageHeaderFetchingException
708
     * @throws Exceptions\EventNotFoundException
709
     */
710
    public function copy($folder_path, $expunge = false) {
711
        $this->client->openFolder($folder_path);
712
        $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

712
        $status = $this->client->getConnection()->/** @scrutinizer ignore-call */ examineFolder($folder_path);
Loading history...
713
714
        if (isset($status["uidnext"])) {
715
            $next_uid = $status["uidnext"];
716
717
            /** @var Folder $folder */
718
            $folder = $this->client->getFolder($folder_path);
719
720
            $this->client->openFolder($this->folder_path);
721
            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

721
            if ($this->client->getConnection()->/** @scrutinizer ignore-call */ copyMessage($folder->path, $this->msgn) == true) {
Loading history...
722
                if($expunge) $this->client->expunge();
723
724
                $this->client->openFolder($folder->path);
725
                $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

725
                $message_num = $this->client->getConnection()->/** @scrutinizer ignore-call */ getMessageNumber($next_uid);
Loading history...
726
727
                $message = $folder->query()->getMessage($message_num);
728
                $event = $this->getEvent("message", "copied");
729
                $event::dispatch($this, $message);
730
731
                return $message;
732
            }
733
        }
734
735
        return null;
736
    }
737
738
    /**
739
     * Move the current Messages to a mailbox
740
     * @param string $folder_path
741
     * @param boolean $expunge
742
     *
743
     * @return Message|null
744
     * @throws Exceptions\ConnectionFailedException
745
     * @throws Exceptions\FolderFetchingException
746
     * @throws Exceptions\RuntimeException
747
     * @throws InvalidMessageDateException
748
     * @throws MessageContentFetchingException
749
     * @throws MessageHeaderFetchingException
750
     * @throws Exceptions\EventNotFoundException
751
     */
752
    public function move($folder_path, $expunge = false) {
753
        $this->client->openFolder($folder_path);
754
        $status = $this->client->getConnection()->examineFolder($folder_path);
755
756
        if (isset($status["uidnext"])) {
757
            $next_uid = $status["uidnext"];
758
759
            /** @var Folder $folder */
760
            $folder = $this->client->getFolder($folder_path);
761
762
            $this->client->openFolder($this->folder_path);
763
            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

763
            if ($this->client->getConnection()->/** @scrutinizer ignore-call */ moveMessage($folder->path, $this->msgn) == true) {
Loading history...
764
                if($expunge) $this->client->expunge();
765
766
                $this->client->openFolder($folder->path);
767
                $message_num = $this->client->getConnection()->getMessageNumber($next_uid);
768
769
                $message = $folder->query()->getMessage($message_num);
770
                $event = $this->getEvent("message", "moved");
771
                $event::dispatch($this, $message);
772
773
                return $message;
774
            }
775
        }
776
777
        return null;
778
    }
779
780
    /**
781
     * Delete the current Message
782
     * @param bool $expunge
783
     *
784
     * @return bool
785
     * @throws Exceptions\ConnectionFailedException
786
     * @throws Exceptions\RuntimeException
787
     * @throws Exceptions\EventNotFoundException
788
     */
789
    public function delete($expunge = true) {
790
        $status = $this->setFlag("Deleted");
791
        if($expunge) $this->client->expunge();
792
793
        $event = $this->getEvent("message", "deleted");
794
        $event::dispatch($this);
795
796
        return $status;
797
    }
798
799
    /**
800
     * Restore a deleted Message
801
     * @param boolean $expunge
802
     *
803
     * @return bool
804
     * @throws Exceptions\ConnectionFailedException
805
     * @throws Exceptions\RuntimeException
806
     * @throws Exceptions\EventNotFoundException
807
     */
808
    public function restore($expunge = true) {
809
        $status = $this->unsetFlag("Deleted");
810
        if($expunge) $this->client->expunge();
811
812
        $event = $this->getEvent("message", "restored");
813
        $event::dispatch($this);
814
815
        return $status;
816
    }
817
818
    /**
819
     * Set a given flag
820
     * @param string|array $flag
821
     *
822
     * @return bool
823
     * @throws Exceptions\ConnectionFailedException
824
     * @throws Exceptions\RuntimeException
825
     * @throws Exceptions\EventNotFoundException
826
     */
827
    public function setFlag($flag) {
828
        $this->client->openFolder($this->folder_path);
829
        $flag = "\\".trim(is_array($flag) ? implode(" \\", $flag) : $flag);
830
        $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

830
        $status = $this->client->getConnection()->/** @scrutinizer ignore-call */ store([$flag], $this->msgn, $this->msgn, "+");
Loading history...
831
        $this->parseFlags();
832
833
        $event = $this->getEvent("flag", "new");
834
        $event::dispatch($this, $flag);
835
836
        return $status;
837
    }
838
839
    /**
840
     * Unset a given flag
841
     * @param string|array $flag
842
     *
843
     * @return bool
844
     * @throws Exceptions\ConnectionFailedException
845
     * @throws Exceptions\RuntimeException
846
     * @throws Exceptions\EventNotFoundException
847
     */
848
    public function unsetFlag($flag) {
849
        $this->client->openFolder($this->folder_path);
850
851
        $flag = "\\".trim(is_array($flag) ? implode(" \\", $flag) : $flag);
852
        $status = $this->client->getConnection()->store([$flag], $this->msgn, $this->msgn, "-");
853
        $this->parseFlags();
854
855
        $event = $this->getEvent("flag", "deleted");
856
        $event::dispatch($this, $flag);
857
858
        return $status;
859
    }
860
861
    /**
862
     * Get all message attachments.
863
     *
864
     * @return AttachmentCollection
865
     */
866
    public function getAttachments() {
867
        return $this->attachments;
868
    }
869
870
    /**
871
     * Checks if there are any attachments present
872
     *
873
     * @return boolean
874
     */
875
    public function hasAttachments() {
876
        return $this->attachments->isEmpty() === false;
877
    }
878
879
    /**
880
     * Get the raw body
881
     *
882
     * @return string
883
     * @throws Exceptions\ConnectionFailedException
884
     */
885
    public function getRawBody() {
886
        if ($this->raw_body === null) {
887
            $this->client->openFolder($this->folder_path);
888
889
            $this->raw_body = $this->structure->raw;
890
        }
891
892
        return $this->raw_body;
893
    }
894
895
    /**
896
     * Get the message header
897
     *
898
     * @return Header
899
     */
900
    public function getHeader() {
901
        return $this->header;
902
    }
903
904
    /**
905
     * Get the current client
906
     *
907
     * @return Client
908
     */
909
    public function getClient() {
910
        return $this->client;
911
    }
912
913
    /**
914
     * Get the used fetch option
915
     *
916
     * @return integer
917
     */
918
    public function getFetchOptions() {
919
        return $this->fetch_options;
920
    }
921
922
    /**
923
     * Get the used fetch body option
924
     *
925
     * @return boolean
926
     */
927
    public function getFetchBodyOption() {
928
        return $this->fetch_body;
929
    }
930
931
    /**
932
     * Get the used fetch flags option
933
     *
934
     * @return boolean
935
     */
936
    public function getFetchFlagsOption() {
937
        return $this->fetch_flags;
938
    }
939
940
    /**
941
     * Get all available bodies
942
     *
943
     * @return array
944
     */
945
    public function getBodies() {
946
        return $this->bodies;
947
    }
948
949
    /**
950
     * Get all set flags
951
     *
952
     * @return FlagCollection
953
     */
954
    public function getFlags() {
955
        return $this->flags;
956
    }
957
958
    /**
959
     * Get the fetched structure
960
     *
961
     * @return object|null
962
     */
963
    public function getStructure(){
964
        return $this->structure;
965
    }
966
967
    /**
968
     * Check if a message matches an other by comparing basic attributes
969
     *
970
     * @param  null|Message $message
971
     * @return boolean
972
     */
973
    public function is(Message $message = null) {
974
        if (is_null($message)) {
975
            return false;
976
        }
977
978
        return $this->uid == $message->uid
979
            && $this->message_id == $message->message_id
980
            && $this->subject == $message->subject
981
            && $this->date->eq($message->date);
982
    }
983
984
    /**
985
     * Get all message attributes
986
     *
987
     * @return array
988
     */
989
    public function getAttributes(){
990
        return array_merge($this->attributes, $this->header->getAttributes());
991
    }
992
993
    /**
994
     * Set the message mask
995
     * @param $mask
996
     *
997
     * @return $this
998
     */
999
    public function setMask($mask){
1000
        if(class_exists($mask)){
1001
            $this->mask = $mask;
1002
        }
1003
1004
        return $this;
1005
    }
1006
1007
    /**
1008
     * Get the used message mask
1009
     *
1010
     * @return string
1011
     */
1012
    public function getMask(){
1013
        return $this->mask;
1014
    }
1015
1016
    /**
1017
     * Get a masked instance by providing a mask name
1018
     * @param string|null $mask
1019
     *
1020
     * @return mixed
1021
     * @throws MaskNotFoundException
1022
     */
1023
    public function mask($mask = null){
1024
        $mask = $mask !== null ? $mask : $this->mask;
1025
        if(class_exists($mask)){
1026
            return new $mask($this);
1027
        }
1028
1029
        throw new MaskNotFoundException("Unknown mask provided: ".$mask);
1030
    }
1031
1032
    /**
1033
     * Set the message path aka folder path
1034
     * @param $folder_path
1035
     *
1036
     * @return $this
1037
     */
1038
    public function setFolderPath($folder_path){
1039
        $this->folder_path = $folder_path;
1040
1041
        return $this;
1042
    }
1043
1044
    /**
1045
     * Set the config
1046
     * @param $config
1047
     *
1048
     * @return $this
1049
     */
1050
    public function setConfig($config){
1051
        $this->config = $config;
1052
1053
        return $this;
1054
    }
1055
1056
    /**
1057
     * Set the attachment collection
1058
     * @param $attachments
1059
     *
1060
     * @return $this
1061
     */
1062
    public function setAttachments($attachments){
1063
        $this->attachments = $attachments;
1064
1065
        return $this;
1066
    }
1067
1068
    /**
1069
     * Set the flag collection
1070
     * @param $flags
1071
     *
1072
     * @return $this
1073
     */
1074
    public function setFlags($flags){
1075
        $this->flags = $flags;
1076
1077
        return $this;
1078
    }
1079
1080
    /**
1081
     * Set the client
1082
     * @param $client
1083
     *
1084
     * @throws Exceptions\ConnectionFailedException
1085
     * @return $this
1086
     */
1087
    public function setClient($client){
1088
        $this->client = $client;
1089
        $this->client->openFolder($this->folder_path);
1090
1091
        return $this;
1092
    }
1093
1094
    /**
1095
     * Set the message number
1096
     * @param $msgn
1097
     * @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...
1098
     *
1099
     * @throws Exceptions\ConnectionFailedException
1100
     * @throws Exceptions\RuntimeException
1101
     * @return $this
1102
     */
1103
    public function setMsgn($msgn, $msglist = null){
1104
        $this->msgn = $msgn;
1105
        $this->msglist = $msglist;
1106
1107
        $this->uid = $this->client->getConnection()->getUid($this->msgn);
1108
1109
        return $this;
1110
    }
1111
}
1112