Passed
Pull Request — master (#156)
by
unknown
03:16
created

Message::getFrom()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 2
rs 10
c 0
b 0
f 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\IMAP;
14
15
use Carbon\Carbon;
16
use Webklex\IMAP\Support\AttachmentCollection;
17
use Webklex\IMAP\Support\FlagCollection;
18
19
/**
20
 * Class Message
21
 *
22
 * @package Webklex\IMAP
23
 */
24
class Message {
25
26
    /**
27
     * Client instance
28
     *
29
     * @var Client
30
     */
31
    private $client = Client::class;
32
33
    /**
34
     * U ID
35
     *
36
     * @var integer
37
     */
38
    public $uid = '';
39
40
    /**
41
     * Fetch body options
42
     *
43
     * @var integer
44
     */
45
    public $fetch_options = null;
46
47
    /**
48
     * Fetch body options
49
     *
50
     * @var bool
51
     */
52
    public $fetch_body = null;
53
54
    /**
55
     * Fetch attachments options
56
     *
57
     * @var bool
58
     */
59
    public $fetch_attachment = null;
60
61
    /**
62
     * Fetch flags options
63
     *
64
     * @var bool
65
     */
66
    public $fetch_flags = null;
67
    
68
    /**
69
     * @var int $msglist
70
     */
71
    public $msglist = 1;
72
73
    /**
74
     * @var int $msgn
75
     */
76
    public $msgn = null;
77
78
    /**
79
     * @var string $header
80
     */
81
    public $header = null;
82
83
    /**
84
     * @var null|object $header_info
85
     */
86
    public $header_info = null;
87
88
    /** @var null|string $raw_body */
89
    public $raw_body = null;
90
91
    /**
92
     * Message header components
93
     *
94
     * @var string  $message_id
95
     * @var mixed   $message_no
96
     * @var string  $subject
97
     * @var mixed   $references
98
     * @var mixed   $date
99
     * @var array   $from
100
     * @var array   $to
101
     * @var array   $cc
102
     * @var array   $bcc
103
     * @var array   $reply_to
104
     * @var string  $in_reply_to
105
     * @var array   $sender
106
     * @var array   $flags
107
     * @var array   $priority
108
     */
109
    public $message_id = '';
110
    public $message_no = null;
111
    public $subject = '';
112
    public $references = null;
113
    public $date = null;
114
    public $from = [];
115
    public $to = [];
116
    public $cc = [];
117
    public $bcc = [];
118
    public $reply_to = [];
119
    public $in_reply_to = '';
120
    public $sender = [];
121
    public $priority = 0;
122
123
    /**
124
     * Message body components
125
     *
126
     * @var array   $bodies
127
     * @var AttachmentCollection|array $attachments
128
     * @var FlagCollection|array       $flags
129
     */
130
    public $bodies = [];
131
    public $attachments = [];
132
    public $flags = [];
133
134
    /**
135
     * Message const
136
     *
137
     * @const integer   TYPE_TEXT
138
     * @const integer   TYPE_MULTIPART
139
     *
140
     * @const integer   ENC_7BIT
141
     * @const integer   ENC_8BIT
142
     * @const integer   ENC_BINARY
143
     * @const integer   ENC_BASE64
144
     * @const integer   ENC_QUOTED_PRINTABLE
145
     * @const integer   ENC_OTHER
146
     */
147
    const TYPE_TEXT = 0;
148
    const TYPE_MULTIPART = 1;
149
150
    const ENC_7BIT = 0;
151
    const ENC_8BIT = 1;
152
    const ENC_BINARY = 2;
153
    const ENC_BASE64 = 3;
154
    const ENC_QUOTED_PRINTABLE = 4;
155
    const ENC_OTHER = 5;
156
157
    const PRIORITY_UNKNOWN = 0;
158
    const PRIORITY_HIGHEST = 1;
159
    const PRIORITY_HIGH = 2;
160
    const PRIORITY_NORMAL = 3;
161
    const PRIORITY_LOW = 4;
162
    const PRIORITY_LOWEST = 5;
163
164
    /**
165
     * Message constructor.
166
     *
167
     * @param integer       $uid
168
     * @param integer|null  $msglist
169
     * @param Client        $client
170
     * @param integer|null  $fetch_options
171
     * @param boolean       $fetch_body
172
     * @param boolean       $fetch_attachment
173
     * @param boolean       $fetch_flags
174
     *
175
     * @throws Exceptions\ConnectionFailedException
176
     */
177
    public function __construct($uid, $msglist, Client $client, $fetch_options = null, $fetch_body = false, $fetch_attachment = false, $fetch_flags = false) {
178
        $this->setFetchOption($fetch_options);
179
        $this->setFetchBodyOption($fetch_body);
180
        $this->setFetchAttachmentOption($fetch_attachment);
181
        $this->setFetchFlagsOption($fetch_flags);
182
183
        $this->attachments = AttachmentCollection::make([]);
184
        $this->flags = FlagCollection::make([]);
185
186
        $this->msglist = $msglist;
187
        $this->client = $client;
188
189
        $this->uid =  ($this->fetch_options == FT_UID) ? $uid : $uid;
190
        $this->msgn = ($this->fetch_options == FT_UID) ? imap_msgno($this->client->getConnection(), $uid) : $uid;
0 ignored issues
show
Bug introduced by
It seems like $this->client->getConnection() can also be of type true; however, parameter $imap_stream of imap_msgno() does only seem to accept resource, 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

190
        $this->msgn = ($this->fetch_options == FT_UID) ? imap_msgno(/** @scrutinizer ignore-type */ $this->client->getConnection(), $uid) : $uid;
Loading history...
191
192
        $this->parseHeader();
193
        
194
        if ($this->getFetchFlagsOption() === true) {
195
            $this->parseFlags();
196
        }
197
198
        if ($this->getFetchBodyOption() === true) {
199
            $this->parseBody();
200
        }
201
    }
202
203
    /**
204
     * Copy the current Messages to a mailbox
205
     *
206
     * @param $mailbox
207
     * @param int $options
208
     *
209
     * @return bool
210
     * @throws Exceptions\ConnectionFailedException
211
     */
212
    public function copy($mailbox, $options = 0) {
213
        return imap_mail_copy($this->client->getConnection(), $this->msglist, $mailbox, $options);
0 ignored issues
show
Bug introduced by
It seems like $this->client->getConnection() can also be of type true; however, parameter $imap_stream of imap_mail_copy() does only seem to accept resource, 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

213
        return imap_mail_copy(/** @scrutinizer ignore-type */ $this->client->getConnection(), $this->msglist, $mailbox, $options);
Loading history...
214
    }
215
216
    /**
217
     * Move the current Messages to a mailbox
218
     *
219
     * @param $mailbox
220
     * @param int $options
221
     *
222
     * @return bool
223
     * @throws Exceptions\ConnectionFailedException
224
     */
225
    public function move($mailbox, $options = 0) {
226
        return imap_mail_move($this->client->getConnection(), $this->msglist, $mailbox, $options);
0 ignored issues
show
Bug introduced by
It seems like $this->client->getConnection() can also be of type true; however, parameter $imap_stream of imap_mail_move() does only seem to accept resource, 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

226
        return imap_mail_move(/** @scrutinizer ignore-type */ $this->client->getConnection(), $this->msglist, $mailbox, $options);
Loading history...
227
    }
228
229
    /**
230
     * Check if the Message has a text body
231
     *
232
     * @return bool
233
     */
234
    public function hasTextBody() {
235
        return isset($this->bodies['text']);
236
    }
237
238
    /**
239
     * Get the Message text body
240
     *
241
     * @return mixed
242
     */
243
    public function getTextBody() {
244
        if (!isset($this->bodies['text'])) {
245
            return false;
246
        }
247
248
        return $this->bodies['text']->content;
249
    }
250
251
    /**
252
     * Check if the Message has a html body
253
     *
254
     * @return bool
255
     */
256
    public function hasHTMLBody() {
257
        return isset($this->bodies['html']);
258
    }
259
260
    /**
261
     * Get the Message html body
262
     *
263
     * @var bool $replaceImages
264
     *
265
     * @return mixed
266
     */
267
    public function getHTMLBody($replaceImages = false) {
268
        if (!isset($this->bodies['html'])) {
269
            return false;
270
        }
271
272
        $body = $this->bodies['html']->content;
273
        if ($replaceImages) {
274
            $this->attachments->each(function($oAttachment) use(&$body) {
275
                if ($oAttachment->id && isset($oAttachment->img_src)) {
276
                    $body = str_replace('cid:'.$oAttachment->id, $oAttachment->img_src, $body);
277
                }
278
            });
279
        }
280
281
        return $body;
282
    }
283
284
    /**
285
     * Parse all defined headers
286
     *
287
     * @return void
288
     * @throws Exceptions\ConnectionFailedException
289
     */
290
    private function parseHeader() {
291
        $this->header = $header = imap_fetchheader($this->client->getConnection(), $this->uid, FT_UID);
0 ignored issues
show
Bug introduced by
It seems like $this->client->getConnection() can also be of type true; however, parameter $imap_stream of imap_fetchheader() does only seem to accept resource, 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

291
        $this->header = $header = imap_fetchheader(/** @scrutinizer ignore-type */ $this->client->getConnection(), $this->uid, FT_UID);
Loading history...
292
        if ($this->header) {
293
            $header = imap_rfc822_parse_headers($this->header);
294
        }
295
296
        if(preg_match('/x\-priority\:.*([0-9]{1,2})/i', $this->header, $priority)){
297
            $priority = isset($priority[1]) ? (int) $priority[1] : 0;
298
            switch($priority){
299
                case self::PRIORITY_HIGHEST;
300
                    $this->priority = self::PRIORITY_HIGHEST;
301
                    break;
302
                case self::PRIORITY_HIGH;
303
                    $this->priority = self::PRIORITY_HIGH;
304
                    break;
305
                case self::PRIORITY_NORMAL;
306
                    $this->priority = self::PRIORITY_NORMAL;
307
                    break;
308
                case self::PRIORITY_LOW;
309
                    $this->priority = self::PRIORITY_LOW;
310
                    break;
311
                case self::PRIORITY_LOWEST;
312
                    $this->priority = self::PRIORITY_LOWEST;
313
                    break;
314
                default:
315
                    $this->priority = self::PRIORITY_UNKNOWN;
316
                    break;
317
            }
318
        }
319
320
        if (property_exists($header, 'subject')) {
321
            $this->subject = mb_decode_mimeheader($header->subject);
322
        }
323
324
        if (property_exists($header, 'date')) {
325
            $date = $header->date;
326
327
            /**
328
             * Exception handling for invalid dates
329
             * Will be extended in the future
330
             *
331
             * Currently known invalid formats:
332
             * ^ Datetime                                   ^ Problem                           ^ Cause                 
333
             * | Mon, 20 Nov 2017 20:31:31 +0800 (GMT+8:00) | Double timezone specification     | A Windows feature
334
             * |                                            | and invalid timezone (max 6 char) |
335
             * | 04 Jan 2018 10:12:47 UT                    | Missing letter "C"                | Unknown
336
             * | Thu, 31 May 2018 18:15:00 +0800 (added by) | Non-standard details added by the | Unknown
337
             * |                                            | mail server                       |
338
             *
339
             * Please report any new invalid timestamps to [#45](https://github.com/Webklex/laravel-imap/issues/45)
340
             */
341
            try {
342
                $this->date = Carbon::parse($date);
343
            } catch (\Exception $e) {
344
                switch (true) {
345
                    case preg_match('/([A-Z]{2,3}\,\ [0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{4}\ [0-9]{1,2}\:[0-9]{1,2}\:[0-9]{1,2}\ [\-|\+][0-9]{4}\ \(.*)\)+$/i', $date) > 0:
346
                    case preg_match('/([0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{2,4}\ [0-9]{2}\:[0-9]{2}\:[0-9]{2}\ [A-Z]{2}\ \-[0-9]{2}\:[0-9]{2}\ \([A-Z]{2,3}\ \-[0-9]{2}:[0-9]{2}\))+$/i', $date) > 0:
347
                    $array = explode('(', $date);
348
                        $array = array_reverse($array);
349
                        $date = trim(array_pop($array));
350
                        break;
351
                    case preg_match('/([0-9]{1,2}\ [A-Z]{2,3}\ [0-9]{4}\ [0-9]{1,2}\:[0-9]{1,2}\:[0-9]{1,2}\ UT)+$/i', $date) > 0:
352
                        $date .= 'C';
353
                        break;
354
                }
355
                $this->date = Carbon::parse($date);
356
            }
357
        }
358
359
        if (property_exists($header, 'from')) {
360
            $this->from = $this->parseAddresses($header->from);
361
        }
362
        if (property_exists($header, 'to')) {
363
            $this->to = $this->parseAddresses($header->to);
364
        }
365
        if (property_exists($header, 'cc')) {
366
            $this->cc = $this->parseAddresses($header->cc);
367
        }
368
        if (property_exists($header, 'bcc')) {
369
            $this->bcc = $this->parseAddresses($header->bcc);
370
        }
371
        if (property_exists($header, 'references')) {
372
            $this->references = $header->references;
373
        }
374
375
        if (property_exists($header, 'reply_to')) {
376
            $this->reply_to = $this->parseAddresses($header->reply_to);
377
        }
378
        if (property_exists($header, 'in_reply_to')) {
379
            $this->in_reply_to = str_replace(['<', '>'], '', $header->in_reply_to);
380
        }
381
        if (property_exists($header, 'sender')) {
382
            $this->sender = $this->parseAddresses($header->sender);
383
        }
384
385
        if (property_exists($header, 'message_id')) {
386
            $this->message_id = str_replace(['<', '>'], '', $header->message_id);
387
        }
388
        if (property_exists($header, 'Msgno')) {
389
            $messageNo = (int) trim($header->Msgno);
390
            $this->message_no = ($this->fetch_options == FT_UID) ? $messageNo : imap_msgno($this->client->getConnection(), $messageNo);
0 ignored issues
show
Bug introduced by
It seems like $this->client->getConnection() can also be of type true; however, parameter $imap_stream of imap_msgno() does only seem to accept resource, 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

390
            $this->message_no = ($this->fetch_options == FT_UID) ? $messageNo : imap_msgno(/** @scrutinizer ignore-type */ $this->client->getConnection(), $messageNo);
Loading history...
391
        } else {
392
            $this->message_no = imap_msgno($this->client->getConnection(), $this->getUid());
393
        }
394
    }
395
396
    /**
397
     * Parse additional flags
398
     *
399
     * @return void
400
     * @throws Exceptions\ConnectionFailedException
401
     */
402
    private function parseFlags() {
403
        $flags = imap_fetch_overview($this->client->getConnection(), $this->uid, FT_UID);
0 ignored issues
show
Bug introduced by
It seems like $this->client->getConnection() can also be of type true; however, parameter $imap_stream of imap_fetch_overview() does only seem to accept resource, 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

403
        $flags = imap_fetch_overview(/** @scrutinizer ignore-type */ $this->client->getConnection(), $this->uid, FT_UID);
Loading history...
404
        if (is_array($flags) && isset($flags[0])) {
405
            if (property_exists($flags[0], 'recent')) {
406
                $this->flags->put('recent', $flags[0]->recent);
407
            }
408
            if (property_exists($flags[0], 'flagged')) {
409
                $this->flags->put('flagged', $flags[0]->flagged);
410
            }
411
            if (property_exists($flags[0], 'answered')) {
412
                $this->flags->put('answered', $flags[0]->answered);
413
            }
414
            if (property_exists($flags[0], 'deleted')) {
415
                $this->flags->put('deleted', $flags[0]->deleted);
416
            }
417
            if (property_exists($flags[0], 'seen')) {
418
                $this->flags->put('seen', $flags[0]->seen);
419
            }
420
            if (property_exists($flags[0], 'draft')) {
421
                $this->flags->put('draft', $flags[0]->draft);
422
            }  
423
        }
424
    }
425
    
426
    /**
427
     * Get the current Message header info
428
     *
429
     * @return object
430
     * @throws Exceptions\ConnectionFailedException
431
     */
432
    public function getHeaderInfo() {
433
        if ($this->header_info == null) {
434
            $this->header_info =
435
            $this->header_info = imap_headerinfo($this->client->getConnection(), $this->getMessageNo());
0 ignored issues
show
Bug introduced by
It seems like $this->client->getConnection() can also be of type true; however, parameter $stream_id of imap_headerinfo() does only seem to accept resource, 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

435
            $this->header_info = imap_headerinfo(/** @scrutinizer ignore-type */ $this->client->getConnection(), $this->getMessageNo());
Loading history...
436
        }
437
438
        return $this->header_info;
439
    }
440
441
    /**
442
     * Parse Addresses
443
     *
444
     * @param $list
445
     *
446
     * @return array
447
     */
448
    private function parseAddresses($list) {
449
        $addresses = [];
450
451
        foreach ($list as $item) {
452
            $address = (object) $item;
453
454
            if (!property_exists($address, 'mailbox')) {
455
                $address->mailbox = false;
456
            }
457
            if (!property_exists($address, 'host')) {
458
                $address->host = false;
459
            }
460
            if (!property_exists($address, 'personal')) {
461
                $address->personal = false;
462
            }
463
464
            $address->personal = imap_utf8($address->personal);
465
466
            $address->mail = ($address->mailbox && $address->host) ? $address->mailbox.'@'.$address->host : false;
467
            $address->full = ($address->personal) ? $address->personal.' <'.$address->mail.'>' : $address->mail;
0 ignored issues
show
Bug introduced by
Are you sure $address->mail of type false|string can be used in concatenation? ( Ignorable by Annotation )

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

467
            $address->full = ($address->personal) ? $address->personal.' <'./** @scrutinizer ignore-type */ $address->mail.'>' : $address->mail;
Loading history...
468
469
            $addresses[] = $address;
470
        }
471
472
        return $addresses;
473
    }
474
475
    /**
476
     * Parse the Message body
477
     *
478
     * @return $this
479
     * @throws Exceptions\ConnectionFailedException
480
     */
481
    public function parseBody() {
482
        $structure = imap_fetchstructure($this->client->getConnection(), $this->uid, FT_UID);
0 ignored issues
show
Bug introduced by
It seems like $this->client->getConnection() can also be of type true; however, parameter $imap_stream of imap_fetchstructure() does only seem to accept resource, 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

482
        $structure = imap_fetchstructure(/** @scrutinizer ignore-type */ $this->client->getConnection(), $this->uid, FT_UID);
Loading history...
483
484
        if(property_exists($structure, 'parts')){
485
            $parts = $structure->parts;
486
487
            foreach ($parts as $part)  {
488
                foreach ($part->parameters as $parameter)  {
489
                    if($parameter->attribute == "charset")  {
490
                        $encoding = $parameter->value;
491
492
                        $encoding = preg_replace('/Content-Transfer-Encoding/', '', $encoding);
493
                        $encoding = preg_replace('/iso-8859-8-i/', 'iso-8859-8', $encoding);
494
495
                        $parameter->value = $encoding;
496
                    }
497
                }
498
            }
499
        }
500
501
        $this->fetchStructure($structure);
502
503
        return $this;
504
    }
505
506
    /**
507
     * Fetch the Message structure
508
     *
509
     * @param $structure
510
     * @param mixed $partNumber
511
     *
512
     * @throws Exceptions\ConnectionFailedException
513
     */
514
    private function fetchStructure($structure, $partNumber = null) {
515
        if ($structure->type == self::TYPE_TEXT &&
516
            ($structure->ifdisposition == 0 ||
517
                ($structure->ifdisposition == 1 && !isset($structure->parts) && $partNumber == null)
518
            )
519
        ) {
520
            if ($structure->subtype == "PLAIN") {
521
                if (!$partNumber) {
522
                    $partNumber = 1;
523
                }
524
525
                $encoding = $this->getEncoding($structure);
526
527
                $content = imap_fetchbody($this->client->getConnection(), $this->uid, $partNumber, $this->fetch_options | FT_UID);
0 ignored issues
show
Bug introduced by
It seems like $this->client->getConnection() can also be of type true; however, parameter $imap_stream of imap_fetchbody() does only seem to accept resource, 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

527
                $content = imap_fetchbody(/** @scrutinizer ignore-type */ $this->client->getConnection(), $this->uid, $partNumber, $this->fetch_options | FT_UID);
Loading history...
528
                $content = $this->decodeString($content, $structure->encoding);
529
                $content = $this->convertEncoding($content, $encoding);
530
531
                $body = new \stdClass;
532
                $body->type = "text";
533
                $body->content = $content;
534
535
                $this->bodies['text'] = $body;
536
537
                $this->fetchAttachment($structure, $partNumber);
538
539
            } elseif ($structure->subtype == "HTML") {
540
                if (!$partNumber) {
541
                    $partNumber = 1;
542
                }
543
544
                $encoding = $this->getEncoding($structure);
545
546
                $content = imap_fetchbody($this->client->getConnection(), $this->uid, $partNumber, $this->fetch_options | FT_UID);
547
                $content = $this->decodeString($content, $structure->encoding);
548
                $content = $this->convertEncoding($content, $encoding);
549
550
                $body = new \stdClass;
551
                $body->type = "html";
552
                $body->content = $content;
553
554
                $this->bodies['html'] = $body;
555
            }
556
        } elseif ($structure->type == self::TYPE_MULTIPART) {
557
            foreach ($structure->parts as $index => $subStruct) {
558
                $prefix = "";
559
                if ($partNumber) {
560
                    $prefix = $partNumber.".";
561
                }
562
                $this->fetchStructure($subStruct, $prefix.($index + 1));
563
            }
564
        } else {
565
            if ($this->getFetchAttachmentOption() === true) {
566
                $this->fetchAttachment($structure, $partNumber);
567
            }
568
        }
569
    }
570
571
    /**
572
     * Fetch the Message attachment
573
     *
574
     * @param object $structure
575
     * @param mixed  $partNumber
576
     *
577
     * @throws Exceptions\ConnectionFailedException
578
     */
579
    protected function fetchAttachment($structure, $partNumber) {
580
581
        $oAttachment = new Attachment($this, $structure, $partNumber);
582
583
        if ($oAttachment->getName() !== null) {
584
            if ($oAttachment->getId() !== null) {
585
                $this->attachments->put($oAttachment->getId(), $oAttachment);
586
            } else {
587
                $this->attachments->push($oAttachment);
588
            }
589
        }
590
    }
591
592
    /**
593
     * Fail proof setter for $fetch_option
594
     *
595
     * @param $option
596
     *
597
     * @return $this
598
     */
599
    public function setFetchOption($option) {
600
        if (is_long($option) === true) {
601
            $this->fetch_options = $option;
602
        } elseif (is_null($option) === true) {
603
            $config = config('imap.options.fetch', FT_UID);
604
            $this->fetch_options = is_long($config) ? $config : 1;
605
        }
606
607
        return $this;
608
    }
609
610
    /**
611
     * Fail proof setter for $fetch_body
612
     *
613
     * @param $option
614
     *
615
     * @return $this
616
     */
617
    public function setFetchBodyOption($option) {
618
        if (is_bool($option)) {
619
            $this->fetch_body = $option;
620
        } elseif (is_null($option)) {
621
            $config = config('imap.options.fetch_body', true);
622
            $this->fetch_body = is_bool($config) ? $config : true;
623
        }
624
625
        return $this;
626
    }
627
628
    /**
629
     * Fail proof setter for $fetch_attachment
630
     *
631
     * @param $option
632
     *
633
     * @return $this
634
     */
635
    public function setFetchAttachmentOption($option) {
636
        if (is_bool($option)) {
637
            $this->fetch_attachment = $option;
638
        } elseif (is_null($option)) {
639
            $config = config('imap.options.fetch_attachment', true);
640
            $this->fetch_attachment = is_bool($config) ? $config : true;
641
        }
642
643
        return $this;
644
    }
645
    
646
    /**
647
     * Fail proof setter for $fetch_flags
648
     *
649
     * @param $option
650
     *
651
     * @return $this
652
     */
653
    public function setFetchFlagsOption($option) {
654
        if (is_bool($option)) {
655
            $this->fetch_flags = $option;
656
        } elseif (is_null($option)) {
657
            $config = config('imap.options.fetch_flags', true);
658
            $this->fetch_flags = is_bool($config) ? $config : true;
659
        }
660
661
        return $this;
662
    }
663
664
    /**
665
     * Decode a given string
666
     *
667
     * @param $string
668
     * @param $encoding
669
     *
670
     * @return string
671
     */
672
    public function decodeString($string, $encoding) {
673
        switch ($encoding) {
674
            case self::ENC_7BIT:
675
                return $string;
676
            case self::ENC_8BIT:
677
                return quoted_printable_decode(imap_8bit($string));
678
            case self::ENC_BINARY:
679
                return imap_binary($string);
680
            case self::ENC_BASE64:
681
                return imap_base64($string);
682
            case self::ENC_QUOTED_PRINTABLE:
683
                return quoted_printable_decode($string);
684
            case self::ENC_OTHER:
685
                return $string;
686
            default:
687
                return $string;
688
        }
689
    }
690
    
691
    /**
692
     * Convert the encoding
693
     *
694
     * @param $str
695
     * @param string $from
696
     * @param string $to
697
     *
698
     * @return mixed|string
699
     */
700
    public function convertEncoding($str, $from = "ISO-8859-2", $to = "UTF-8") {
701
702
        $from = EncodingAliases::get($from);
703
        $to = EncodingAliases::get($to);
704
705
        // We don't need to do convertEncoding() if charset is ASCII (us-ascii):
706
        //     ASCII is a subset of UTF-8, so all ASCII files are already UTF-8 encoded
707
        //     https://stackoverflow.com/a/11303410
708
        // 
709
        // us-ascii is the same as ASCII:
710
        //     ASCII is the traditional name for the encoding system; the Internet Assigned Numbers Authority (IANA) 
711
        //     prefers the updated name US-ASCII, which clarifies that this system was developed in the US and 
712
        //     based on the typographical symbols predominantly in use there.
713
        //     https://en.wikipedia.org/wiki/ASCII
714
        //
715
        // convertEncoding() function basically means convertToUtf8(), so when we convert ASCII string into UTF-8 it gets broken.
716
        if (strtolower($from) == 'us-ascii' && $to == 'UTF-8') {
717
            return $str;
718
        }
719
720
        if (function_exists('iconv') && $from != 'UTF-7' && $to != 'UTF-7') {
721
            return iconv($from, $to.'//IGNORE', $str);
722
        } else {
723
            if (!$from) {
724
                return mb_convert_encoding($str, $to);
725
            }
726
            return mb_convert_encoding($str, $to, $from);
727
        }
728
    }
729
730
    /**
731
     * Get the encoding of a given abject
732
     *
733
     * @param object|string $structure
734
     *
735
     * @return string
736
     */
737
    public function getEncoding($structure) {
738
        if (property_exists($structure, 'parameters')) {
739
            foreach ($structure->parameters as $parameter) {
740
                if (strtolower($parameter->attribute) == "charset") {
741
                    return EncodingAliases::get($parameter->value);
742
                }
743
            }
744
        }elseif (is_string($structure) === true){
745
            return mb_detect_encoding($structure);
746
        }
747
748
        return 'UTF-8';
749
    }
750
751
    /**
752
     * Find the folder containing this message.
753
     *
754
     * @param null|Folder $folder where to start searching from (top-level inbox by default)
755
     *
756
     * @return null|Folder
757
     * @throws Exceptions\ConnectionFailedException
758
     */
759
    public function getContainingFolder(Folder $folder = null) {
760
        $folder = $folder ?: $this->client->getFolders()->first();
761
        $this->client->checkConnection();
762
763
        // Try finding the message by uid in the current folder
764
        $client = new Client;
765
        $client->openFolder($folder);
766
        $uidMatches = imap_fetch_overview($client->getConnection(), $this->uid, FT_UID);
0 ignored issues
show
Bug introduced by
It seems like $client->getConnection() can also be of type true; however, parameter $imap_stream of imap_fetch_overview() does only seem to accept resource, 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

766
        $uidMatches = imap_fetch_overview(/** @scrutinizer ignore-type */ $client->getConnection(), $this->uid, FT_UID);
Loading history...
767
        $uidMatch = count($uidMatches)
768
            ? new Message($uidMatches[0]->uid, $uidMatches[0]->msgno, $client)
769
            : null;
770
        $client->disconnect();
771
772
        // imap_fetch_overview() on a parent folder will return the matching message
773
        // even when the message is in a child folder so we need to recursively
774
        // search the children
775
        foreach ($folder->children as $child) {
776
            $childFolder = $this->getContainingFolder($child);
777
778
            if ($childFolder) {
779
                return $childFolder;
780
            }
781
        }
782
783
        // before returning the parent
784
        if ($this->is($uidMatch)) {
785
            return $folder;
786
        }
787
788
        // or signalling that the message was not found in any folder
789
        return null;
790
    }
791
792
    /**
793
     * Move the Message into an other Folder
794
     * @param string $mailbox
795
     *
796
     * @return bool
797
     * @throws Exceptions\ConnectionFailedException
798
     */
799
    public function moveToFolder($mailbox = 'INBOX') {
800
        $this->client->createFolder($mailbox);
801
802
        return imap_mail_move($this->client->getConnection(), $this->uid, $mailbox, CP_UID);
0 ignored issues
show
Bug introduced by
It seems like $this->client->getConnection() can also be of type true; however, parameter $imap_stream of imap_mail_move() does only seem to accept resource, 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

802
        return imap_mail_move(/** @scrutinizer ignore-type */ $this->client->getConnection(), $this->uid, $mailbox, CP_UID);
Loading history...
803
    }
804
805
    /**
806
     * Delete the current Message
807
     * @param bool $expunge
808
     *
809
     * @return bool
810
     * @throws Exceptions\ConnectionFailedException
811
     */
812
    public function delete($expunge = true) {
813
        $status = imap_delete($this->client->getConnection(), $this->uid, FT_UID);
0 ignored issues
show
Bug introduced by
It seems like $this->client->getConnection() can also be of type true; however, parameter $imap_stream of imap_delete() does only seem to accept resource, 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

813
        $status = imap_delete(/** @scrutinizer ignore-type */ $this->client->getConnection(), $this->uid, FT_UID);
Loading history...
814
        if($expunge) $this->client->expunge();
815
816
        return $status;
817
    }
818
819
    /**
820
     * Restore a deleted Message
821
     * @param boolean $expunge
822
     *
823
     * @return bool
824
     * @throws Exceptions\ConnectionFailedException
825
     */
826
    public function restore($expunge = true) {
827
        $status = imap_undelete($this->client->getConnection(), $this->uid, FT_UID);
0 ignored issues
show
Bug introduced by
It seems like $this->client->getConnection() can also be of type true; however, parameter $imap_stream of imap_undelete() does only seem to accept resource, 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

827
        $status = imap_undelete(/** @scrutinizer ignore-type */ $this->client->getConnection(), $this->uid, FT_UID);
Loading history...
828
        if($expunge) $this->client->expunge();
829
830
        return $status;
831
    }
832
833
    /**
834
     * Get all message attachments.
835
     *
836
     * @return AttachmentCollection
837
     */
838
    public function getAttachments() {
839
        return $this->attachments;
840
    }
841
842
    /**
843
     * Checks if there are any attachments present
844
     *
845
     * @return boolean
846
     */
847
    public function hasAttachments() {
848
        return $this->attachments->isEmpty() === false;
849
    }
850
851
    /**
852
     * Set a given flag
853
     * @param string|array $flag
854
     *
855
     * @return bool
856
     * @throws Exceptions\ConnectionFailedException
857
     */
858
    public function setFlag($flag) {
859
        $flag = "\\".trim(is_array($flag) ? implode(" \\", $flag) : $flag);
860
        $status = imap_setflag_full($this->client->getConnection(), $this->getUid(), $flag, SE_UID);
0 ignored issues
show
Bug introduced by
It seems like $this->client->getConnection() can also be of type true; however, parameter $imap_stream of imap_setflag_full() does only seem to accept resource, 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

860
        $status = imap_setflag_full(/** @scrutinizer ignore-type */ $this->client->getConnection(), $this->getUid(), $flag, SE_UID);
Loading history...
861
        $this->parseFlags();
862
863
        return $status;
864
    }
865
866
    /**
867
     * Unset a given flag
868
     * @param string|array $flag
869
     *
870
     * @return bool
871
     * @throws Exceptions\ConnectionFailedException
872
     */
873
    public function unsetFlag($flag) {
874
        $flag = "\\".trim(is_array($flag) ? implode(" \\", $flag) : $flag);
875
        $status = imap_clearflag_full($this->client->getConnection(), $this->getUid(), $flag, SE_UID);
0 ignored issues
show
Bug introduced by
It seems like $this->client->getConnection() can also be of type true; however, parameter $imap_stream of imap_clearflag_full() does only seem to accept resource, 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

875
        $status = imap_clearflag_full(/** @scrutinizer ignore-type */ $this->client->getConnection(), $this->getUid(), $flag, SE_UID);
Loading history...
876
        $this->parseFlags();
877
878
        return $status;
879
    }
880
881
    /**
882
     * @return null|object|string
883
     * @throws Exceptions\ConnectionFailedException
884
     */
885
    public function getRawBody() {
886
        if ($this->raw_body === null) {
887
            $this->raw_body = imap_fetchbody($this->client->getConnection(), $this->getUid(), '', $this->fetch_options | FT_UID);
0 ignored issues
show
Bug introduced by
It seems like $this->client->getConnection() can also be of type true; however, parameter $imap_stream of imap_fetchbody() does only seem to accept resource, 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

887
            $this->raw_body = imap_fetchbody(/** @scrutinizer ignore-type */ $this->client->getConnection(), $this->getUid(), '', $this->fetch_options | FT_UID);
Loading history...
888
        }
889
890
        return $this->raw_body;
891
    }
892
893
    /**
894
     * @return string
895
     */
896
    public function getHeader() {
897
        return $this->header;
898
    }
899
900
    /**
901
     * @return Client
902
     */
903
    public function getClient() {
904
        return $this->client;
905
    }
906
907
    /**
908
     * @return integer
909
     */
910
    public function getUid() {
911
        return $this->uid;
912
    }
913
914
    /**
915
     * @return integer
916
     */
917
    public function getFetchOptions() {
918
        return $this->fetch_options;
919
    }
920
921
    /**
922
     * @return boolean
923
     */
924
    public function getFetchBodyOption() {
925
        return $this->fetch_body;
926
    }
927
928
    /**
929
     * @return integer
930
     */
931
    public function getPriority() {
932
        return $this->priority;
933
    }
934
935
    /**
936
     * @return boolean
937
     */
938
    public function getFetchAttachmentOption() {
939
        return $this->fetch_attachment;
940
    }
941
    
942
    /**
943
     * @return boolean
944
     */
945
    public function getFetchFlagsOption() {
946
        return $this->fetch_flags;
947
    }
948
949
    /**
950
     * @return int
951
     */
952
    public function getMsglist() {
953
        return $this->msglist;
954
    }
955
956
    /**
957
     * @return mixed
958
     */
959
    public function getMessageId() {
960
        return $this->message_id;
961
    }
962
963
    /**
964
     * @return int
965
     */
966
    public function getMessageNo() {
967
        return $this->message_no;
968
    }
969
970
    /**
971
     * @return string
972
     */
973
    public function getSubject() {
974
        return $this->subject;
975
    }
976
977
    /**
978
     * @return mixed
979
     */
980
    public function getReferences() {
981
        return $this->references;
982
    }
983
984
    /**
985
     * @return Carbon|null
986
     */
987
    public function getDate() {
988
        return $this->date;
989
    }
990
991
    /**
992
     * @return array
993
     */
994
    public function getFrom() {
995
        return $this->from;
996
    }
997
998
    /**
999
     * @return array
1000
     */
1001
    public function getTo() {
1002
        return $this->to;
1003
    }
1004
1005
    /**
1006
     * @return array
1007
     */
1008
    public function getCc() {
1009
        return $this->cc;
1010
    }
1011
1012
    /**
1013
     * @return array
1014
     */
1015
    public function getBcc() {
1016
        return $this->bcc;
1017
    }
1018
1019
    /**
1020
     * @return array
1021
     */
1022
    public function getReplyTo() {
1023
        return $this->reply_to;
1024
    }
1025
    
1026
    /**
1027
     * @return string
1028
     */
1029
    public function getInReplyTo() {
1030
        return $this->in_reply_to;
1031
    }
1032
1033
    /**
1034
     * @return array
1035
     */
1036
    public function getSender() {
1037
        return $this->sender;
1038
    }
1039
1040
    /**
1041
     * @return mixed
1042
     */
1043
    public function getBodies() {
1044
        return $this->bodies;
1045
    }
1046
    
1047
    /**
1048
     * @return FlagCollection
1049
     */
1050
    public function getFlags() {
1051
        return $this->flags;
1052
    }
1053
1054
    /**
1055
     * Does this message match another one?
1056
     *
1057
     * A match means same uid, message id, subject and date/time.
1058
     *
1059
     * @param  null|static $message
1060
     * @return boolean
1061
     */
1062
    public function is(Message $message = null) {
1063
        if (is_null($message)) {
1064
            return false;
1065
        }
1066
1067
        return $this->uid == $message->uid
1068
            && $this->message_id == $message->message_id
1069
            && $this->subject == $message->subject
1070
            && $this->date->eq($message->date);
1071
    }
1072
}
1073