Passed
Push — master ( 81c85c...b3bf46 )
by Malte
02:06
created

Message::make()   B

Complexity

Conditions 6
Paths 16

Size

Total Lines 42
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 28
c 0
b 0
f 0
dl 0
loc 42
rs 8.8497
cc 6
nc 16
nop 8

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

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

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
210
211
        $this->config = ClientManager::get('options');
212
213
        $this->setSequence($sequence);
214
        $this->setFetchOption($fetch_options);
215
        $this->setFetchBodyOption($fetch_body);
216
        $this->setFetchFlagsOption($fetch_flags);
217
218
        $this->attachments = AttachmentCollection::make([]);
219
        $this->flags = FlagCollection::make([]);
220
221
        $this->client = $client;
222
        $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

222
        $this->client->openFolder(/** @scrutinizer ignore-type */ $this->folder_path);
Loading history...
223
224
        if ($this->sequence === IMAP::ST_UID) {
225
            $this->uid = $uid;
226
            $this->msgn = $this->client->getConnection()->getMessageNumber($this->uid);
0 ignored issues
show
Bug introduced by
The method getMessageNumber() does not exist on Webklex\PHPIMAP\Connection\Protocols\Protocol. Since it exists in all sub-types, consider adding an abstract or default implementation to Webklex\PHPIMAP\Connection\Protocols\Protocol. ( Ignorable by Annotation )

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

226
            $this->msgn = $this->client->getConnection()->/** @scrutinizer ignore-call */ getMessageNumber($this->uid);
Loading history...
227
        }else{
228
            $this->msgn = $uid;
229
            $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

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

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

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

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

428
        $headers = $this->client->getConnection()->/** @scrutinizer ignore-call */ headers([$sequence_id], $this->sequence === IMAP::ST_UID);
Loading history...
429
        if (!isset($headers[$sequence_id])) {
430
            throw new MessageHeaderFetchingException("no headers found", 0);
431
        }
432
433
        $this->parseRawHeader($headers[$sequence_id]);
434
    }
435
436
    /**
437
     * @param string $raw_header
438
     *
439
     * @throws InvalidMessageDateException
440
     */
441
    public function parseRawHeader($raw_header){
442
        $this->header = new Header($raw_header);
443
    }
444
445
    /**
446
     * Parse additional raw flags
447
     * @param $raw_flags
448
     */
449
    public function parseRawFlags($raw_flags) {
450
        $this->flags = FlagCollection::make([]);
451
452
        foreach($raw_flags as $flag) {
453
            if (strpos($flag, "\\") === 0){
454
                $flag = substr($flag, 1);
455
            }
456
            $flag_key = strtolower($flag);
457
            if (in_array($flag_key, $this->available_flags)) {
458
                $this->flags->put($flag_key, $flag);
459
            }
460
        }
461
    }
462
463
    /**
464
     * Parse additional flags
465
     *
466
     * @return void
467
     * @throws Exceptions\ConnectionFailedException
468
     * @throws Exceptions\RuntimeException
469
     */
470
    private function parseFlags() {
471
        $this->client->openFolder($this->folder_path);
472
        $this->flags = FlagCollection::make([]);
473
474
        $sequence_id = $this->getSequenceId();
475
        $flags = $this->client->getConnection()->flags([$sequence_id], $this->sequence === IMAP::ST_UID);
0 ignored issues
show
Bug introduced by
The method flags() does not exist on Webklex\PHPIMAP\Connection\Protocols\Protocol. Since it exists in all sub-types, consider adding an abstract or default implementation to Webklex\PHPIMAP\Connection\Protocols\Protocol. ( Ignorable by Annotation )

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

475
        $flags = $this->client->getConnection()->/** @scrutinizer ignore-call */ flags([$sequence_id], $this->sequence === IMAP::ST_UID);
Loading history...
476
477
        if (isset($flags[$sequence_id])) {
478
            $this->parseRawFlags($flags[$sequence_id]);
479
        }
480
    }
481
482
    /**
483
     * Parse the Message body
484
     *
485
     * @return $this
486
     * @throws Exceptions\ConnectionFailedException
487
     * @throws Exceptions\MessageContentFetchingException
488
     * @throws InvalidMessageDateException
489
     * @throws Exceptions\RuntimeException
490
     * @throws Exceptions\EventNotFoundException
491
     */
492
    public function parseBody() {
493
        $this->client->openFolder($this->folder_path);
494
495
        $sequence_id = $this->getSequenceId();
496
        $contents = $this->client->getConnection()->content([$sequence_id], $this->sequence === IMAP::ST_UID);
0 ignored issues
show
Bug introduced by
The method content() does not exist on Webklex\PHPIMAP\Connection\Protocols\Protocol. Since it exists in all sub-types, consider adding an abstract or default implementation to Webklex\PHPIMAP\Connection\Protocols\Protocol. ( Ignorable by Annotation )

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

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

842
        $status = $this->client->getConnection()->/** @scrutinizer ignore-call */ examineFolder($folder_path);
Loading history...
843
844
        if (isset($status["uidnext"])) {
845
            $next_uid = $status["uidnext"];
846
847
            /** @var Folder $folder */
848
            $folder = $this->client->getFolder($folder_path);
849
850
            $this->client->openFolder($this->folder_path);
851
            if ($this->client->getConnection()->copyMessage($folder->path, $this->getSequenceId(), null, $this->sequence === IMAP::ST_UID) == true) {
0 ignored issues
show
Bug introduced by
The method copyMessage() does not exist on Webklex\PHPIMAP\Connection\Protocols\Protocol. Since it exists in all sub-types, consider adding an abstract or default implementation to Webklex\PHPIMAP\Connection\Protocols\Protocol. ( Ignorable by Annotation )

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

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

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

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

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

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