Completed
Push — master ( fba832...6b126c )
by Malte
02:17
created

Message::getReferences()   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
18
/**
19
 * Class Message
20
 *
21
 * @package Webklex\IMAP
22
 */
23
class Message {
24
25
    /**
26
     * Client instance
27
     *
28
     * @var Client
29
     */
30
    private $client = Client::class;
31
32
    /**
33
     * U ID
34
     *
35
     * @var integer
36
     */
37
    public $uid = '';
38
39
    /**
40
     * Fetch body options
41
     *
42
     * @var integer
43
     */
44
    public $fetch_options = null;
45
46
    /**
47
     * Fetch body options
48
     *
49
     * @var bool
50
     */
51
    public $fetch_body = null;
52
53
    /**
54
     * Fetch attachments options
55
     *
56
     * @var bool
57
     */
58
    public $fetch_attachment = null;
59
60
    /**
61
     * @var int $msglist
62
     */
63
    public $msglist = 1;
64
65
    /**
66
     * @var string $header
67
     */
68
    public $header = null;
69
70
    /**
71
     * @var null|object $header_info
72
     */
73
    public $header_info = null;
74
75
    /** @var null|object $raw_body */
76
    public $raw_body = null;
77
78
    /**
79
     * Message header components
80
     *
81
     * @var string  $message_id
82
     * @var mixed   $message_no
83
     * @var string  $subject
84
     * @var mixed   $references
85
     * @var mixed   $date
86
     * @var array   $from
87
     * @var array   $to
88
     * @var array   $cc
89
     * @var array   $bcc
90
     * @var array   $reply_to
91
     * @var string  $in_reply_to
92
     * @var array   $sender
93
     */
94
    public $message_id = '';
95
    public $message_no = null;
96
    public $subject = '';
97
    public $references = null;
98
    public $date = null;
99
    public $from = [];
100
    public $to = [];
101
    public $cc = [];
102
    public $bcc = [];
103
    public $reply_to = [];
104
    public $in_reply_to = '';
105
    public $sender = [];
106
107
    /**
108
     * Message body components
109
     *
110
     * @var array   $bodies
111
     * @var AttachmentCollection|array  $attachments
112
     */
113
    public $bodies = [];
114
    public $attachments = [];
115
116
    /**
117
     * Message const
118
     *
119
     * @const integer   TYPE_TEXT
120
     * @const integer   TYPE_MULTIPART
121
     *
122
     * @const integer   ENC_7BIT
123
     * @const integer   ENC_8BIT
124
     * @const integer   ENC_BINARY
125
     * @const integer   ENC_BASE64
126
     * @const integer   ENC_QUOTED_PRINTABLE
127
     * @const integer   ENC_OTHER
128
     */
129
    const TYPE_TEXT = 0;
130
    const TYPE_MULTIPART = 1;
131
132
    const ENC_7BIT = 0;
133
    const ENC_8BIT = 1;
134
    const ENC_BINARY = 2;
135
    const ENC_BASE64 = 3;
136
    const ENC_QUOTED_PRINTABLE = 4;
137
    const ENC_OTHER = 5;
138
139
    /**
140
     * Message constructor.
141
     *
142
     * @param integer       $uid
143
     * @param integer|null  $msglist
144
     * @param Client        $client
145
     * @param integer|null  $fetch_options
146
     * @param boolean       $fetch_body
147
     * @param boolean       $fetch_attachment
148
     */
149
    public function __construct($uid, $msglist, Client $client, $fetch_options = null, $fetch_body = false, $fetch_attachment = false) {
150
        $this->setFetchOption($fetch_options);
151
        $this->setFetchBodyOption($fetch_body);
152
        $this->setFetchAttachmentOption($fetch_attachment);
153
154
        $this->attachments = AttachmentCollection::make([]);
155
        
156
        $this->msglist = $msglist;
157
        $this->client = $client;
158
        $this->uid = ($this->fetch_options == FT_UID) ? $uid : imap_msgno($this->client->getConnection(), $uid);
159
        
160
        $this->parseHeader();
161
162
        if ($this->getFetchBodyOption() === true) {
163
            $this->parseBody();
164
        }
165
    }
166
167
    /**
168
     * Copy the current Messages to a mailbox
169
     *
170
     * @param $mailbox
171
     * @param int $options
172
     *
173
     * @return bool
174
     */
175
    public function copy($mailbox, $options = 0) {
176
        return imap_mail_copy($this->client->getConnection(), $this->msglist, $mailbox, $options);
177
    }
178
179
    /**
180
     * Move the current Messages to a mailbox
181
     *
182
     * @param $mailbox
183
     * @param int $options
184
     *
185
     * @return bool
186
     */
187
    public function move($mailbox, $options = 0) {
188
        return imap_mail_move($this->client->getConnection(), $this->msglist, $mailbox, $options);
189
    }
190
191
    /**
192
     * Check if the Message has a text body
193
     *
194
     * @return bool
195
     */
196
    public function hasTextBody() {
197
        return isset($this->bodies['text']);
198
    }
199
200
    /**
201
     * Get the Message text body
202
     *
203
     * @return mixed
204
     */
205
    public function getTextBody() {
206
        if (!isset($this->bodies['text'])) {
207
            return false;
208
        }
209
210
        return $this->bodies['text']->content;
211
    }
212
213
    /**
214
     * Check if the Message has a html body
215
     *
216
     * @return bool
217
     */
218
    public function hasHTMLBody() {
219
        return isset($this->bodies['html']);
220
    }
221
222
    /**
223
     * Get the Message html body
224
     *
225
     * @var bool $replaceImages
226
     *
227
     * @return mixed
228
     */
229
    public function getHTMLBody($replaceImages = false) {
230
        if (!isset($this->bodies['html'])) {
231
            return false;
232
        }
233
234
        $body = $this->bodies['html']->content;
235
        if ($replaceImages) {
236
            $this->attachments->each(function($oAttachment) use(&$body){
237
                if ($oAttachment->id && isset($oAttachment->img_src)) {
238
                    $body = str_replace('cid:'.$oAttachment->id, $oAttachment->img_src, $body);
239
                }
240
            });
241
        }
242
243
        return $body;
244
    }
245
246
    /**
247
     * Parse all defined headers
248
     *
249
     * @return void
250
     */
251
    private function parseHeader() {
252
        $this->header = $header = imap_fetchheader($this->client->getConnection(), $this->uid, $this->fetch_options);
253
        if ($this->header) {
254
            $header = imap_rfc822_parse_headers($this->header);
255
        }
256
257
        if (property_exists($header, 'subject')) {
258
            $this->subject = imap_utf8($header->subject);
259
        }
260
        if (property_exists($header, 'date')) {
261
            $date = $header->date;
262
263
            /**
264
             * Exception handling for invalid dates
265
             * Will be extended in the future
266
             *
267
             * Currently known invalid formats:
268
             * ^ Datetime                                   ^ Problem                           ^ Cause                 
269
             * | Mon, 20 Nov 2017 20:31:31 +0800 (GMT+8:00) | Double timezone specification     | A Windows feature
270
             * |                                            | and invalid timezone (max 6 char) |
271
             * | 04 Jan 2018 10:12:47 UT                    | Missing letter "C"                | Unknown
272
             *
273
             * Please report any new invalid timestamps to [#45](https://github.com/Webklex/laravel-imap/issues/45)
274
             */
275
            try {
276
                $this->date = Carbon::parse($date);
277
            } catch (\Exception $e) {
278
                switch (true) {
279
                    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}\ \([A-Z]{2,3}\+[0-9]{1,2}\:[0-9]{1,2})\)+$/i', $date) > 0:
280
                        $array = explode('(', $date);
281
                        $array = array_reverse($array);
282
                        $date = trim(array_pop($array));
283
                        break;
284
                    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:
285
                        $date .= 'C';
286
                        break;
287
                }
288
                $this->date = Carbon::parse($date);
289
            }
290
        }
291
292
        if (property_exists($header, 'from')) {
293
            $this->from = $this->parseAddresses($header->from);
294
        }
295
        if (property_exists($header, 'to')) {
296
            $this->to = $this->parseAddresses($header->to);
297
        }
298
        if (property_exists($header, 'cc')) {
299
            $this->cc = $this->parseAddresses($header->cc);
300
        }
301
        if (property_exists($header, 'bcc')) {
302
            $this->bcc = $this->parseAddresses($header->bcc);
303
        }
304
        if (property_exists($header, 'references')) {
305
            $this->references = $header->references;
306
        }
307
308
        if (property_exists($header, 'reply_to')) {
309
            $this->reply_to = $this->parseAddresses($header->reply_to);
310
        }
311
        if (property_exists($header, 'in_reply_to')) {
312
            $this->in_reply_to = str_replace(['<', '>'], '', $header->in_reply_to);
313
        }
314
        if (property_exists($header, 'sender')) {
315
            $this->sender = $this->parseAddresses($header->sender);
316
        }
317
318
        if (property_exists($header, 'message_id')) {
319
            $this->message_id = str_replace(['<', '>'], '', $header->message_id);
320
        }
321
        if (property_exists($header, 'Msgno')) {
322
            $messageNo = (int) trim($header->Msgno);
323
            $this->message_no = ($this->fetch_options == FT_UID) ? $messageNo : imap_msgno($this->client->getConnection(), $messageNo);
324
        } else {
325
            $this->message_no = imap_msgno($this->client->getConnection(), $this->getUid());
326
        }
327
    }
328
329
    /**
330
     * Get the current Message header info
331
     *
332
     * @return object
333
     */
334
    public function getHeaderInfo() {
335
        if ($this->header_info == null) {
336
            $this->header_info =
337
            $this->header_info = imap_headerinfo($this->client->getConnection(), $this->getMessageNo()); ;
338
        }
339
340
        return $this->header_info;
341
    }
342
343
    /**
344
     * Parse Addresses
345
     *
346
     * @param $list
347
     *
348
     * @return array
349
     */
350
    private function parseAddresses($list) {
351
        $addresses = [];
352
353
        foreach ($list as $item) {
354
            $address = (object) $item;
355
356
            if (!property_exists($address, 'mailbox')) {
357
                $address->mailbox = false;
358
            }
359
            if (!property_exists($address, 'host')) {
360
                $address->host = false;
361
            }
362
            if (!property_exists($address, 'personal')) {
363
                $address->personal = false;
364
            }
365
366
            $address->personal = imap_utf8($address->personal);
367
368
            $address->mail = ($address->mailbox && $address->host) ? $address->mailbox.'@'.$address->host : false;
369
            $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

369
            $address->full = ($address->personal) ? $address->personal.' <'./** @scrutinizer ignore-type */ $address->mail.'>' : $address->mail;
Loading history...
370
371
            $addresses[] = $address;
372
        }
373
374
        return $addresses;
375
    }
376
377
    /**
378
     * Parse the Message body
379
     *
380
     * @return $this
381
     */
382
    public function parseBody() {
383
        $structure = imap_fetchstructure($this->client->getConnection(), $this->uid, $this->fetch_options);
384
385
        $this->fetchStructure($structure);
386
387
        return $this;
388
    }
389
390
    /**
391
     * Fetch the Message structure
392
     *
393
     * @param $structure
394
     * @param mixed $partNumber
395
     */
396
    private function fetchStructure($structure, $partNumber = null) {
397
        if ($structure->type == self::TYPE_TEXT &&
398
            ($structure->ifdisposition == 0 ||
399
                ($structure->ifdisposition == 1 && !isset($structure->parts) && $partNumber == null)
400
            )
401
        ) {
402
            if ($structure->subtype == "PLAIN") {
403
                if (!$partNumber) {
404
                    $partNumber = 1;
405
                }
406
407
                $encoding = $this->getEncoding($structure);
408
409
                $content = imap_fetchbody($this->client->getConnection(), $this->uid, $partNumber, $this->fetch_options);
410
                $content = $this->decodeString($content, $structure->encoding);
411
                $content = $this->convertEncoding($content, $encoding);
412
413
                $body = new \stdClass;
414
                $body->type = "text";
415
                $body->content = $content;
416
417
                $this->bodies['text'] = $body;
418
419
                $this->fetchAttachment($structure, $partNumber);
420
421
            } elseif ($structure->subtype == "HTML") {
422
                if (!$partNumber) {
423
                    $partNumber = 1;
424
                }
425
426
                $encoding = $this->getEncoding($structure);
427
428
                $content = imap_fetchbody($this->client->getConnection(), $this->uid, $partNumber, $this->fetch_options);
429
                $content = $this->decodeString($content, $structure->encoding);
430
                $content = $this->convertEncoding($content, $encoding);
431
432
                $body = new \stdClass;
433
                $body->type = "html";
434
                $body->content = $content;
435
436
                $this->bodies['html'] = $body;
437
            }
438
        } elseif ($structure->type == self::TYPE_MULTIPART) {
439
            foreach ($structure->parts as $index => $subStruct) {
440
                $prefix = "";
441
                if ($partNumber) {
442
                    $prefix = $partNumber.".";
443
                }
444
                $this->fetchStructure($subStruct, $prefix.($index + 1));
445
            }
446
        } else {
447
            if ($this->getFetchAttachmentOption() === true) {
448
                $this->fetchAttachment($structure, $partNumber);
449
            }
450
        }
451
    }
452
453
    /**
454
     * Fetch the Message attachment
455
     *
456
     * @param object $structure
457
     * @param mixed  $partNumber
458
     */
459
    protected function fetchAttachment($structure, $partNumber) {
460
461
        $oAttachment = new Attachment($this, $structure, $partNumber);
462
463
        if ($oAttachment->getName() !== null) {
464
            if ($oAttachment->getId() !== null) {
465
                $this->attachments->put($oAttachment->getId(), $oAttachment);
466
            } else {
467
                $this->attachments->push($oAttachment);
468
            }
469
        }
470
    }
471
472
    /**
473
     * Fail proof setter for $fetch_option
474
     *
475
     * @param $option
476
     *
477
     * @return $this
478
     */
479
    public function setFetchOption($option) {
480
        if (is_long($option) === true) {
481
            $this->fetch_options = $option;
482
        } elseif (is_null($option) === true) {
483
            $config = config('imap.options.fetch', FT_UID);
484
            $this->fetch_options = is_long($config) ? $config : 1;
485
        }
486
487
        return $this;
488
    }
489
490
    /**
491
     * Fail proof setter for $fetch_body
492
     *
493
     * @param $option
494
     *
495
     * @return $this
496
     */
497
    public function setFetchBodyOption($option) {
498
        if (is_bool($option)) {
499
            $this->fetch_body = $option;
500
        } elseif (is_null($option)) {
501
            $config = config('imap.options.fetch_body', true);
502
            $this->fetch_body = is_bool($config) ? $config : true;
503
        }
504
505
        return $this;
506
    }
507
508
    /**
509
     * Fail proof setter for $fetch_attachment
510
     *
511
     * @param $option
512
     *
513
     * @return $this
514
     */
515
    public function setFetchAttachmentOption($option) {
516
        if (is_bool($option)) {
517
            $this->fetch_attachment = $option;
518
        } elseif (is_null($option)) {
519
            $config = config('imap.options.fetch_attachment', true);
520
            $this->fetch_attachment = is_bool($config) ? $config : true;
521
        }
522
523
        return $this;
524
    }
525
526
    /**
527
     * Decode a given string
528
     *
529
     * @param $string
530
     * @param $encoding
531
     *
532
     * @return string
533
     */
534
    public function decodeString($string, $encoding) {
535
        switch ($encoding) {
536
            case self::ENC_7BIT:
537
                return $string;
538
            case self::ENC_8BIT:
539
                return quoted_printable_decode(imap_8bit($string));
540
            case self::ENC_BINARY:
541
                return imap_binary($string);
542
            case self::ENC_BASE64:
543
                return imap_base64($string);
544
            case self::ENC_QUOTED_PRINTABLE:
545
                return quoted_printable_decode($string);
546
            case self::ENC_OTHER:
547
                return $string;
548
            default:
549
                return $string;
550
        }
551
    }
552
553
    /**
554
     * Convert the encoding
555
     *
556
     * @param $str
557
     * @param string $from
558
     * @param string $to
559
     *
560
     * @return mixed|string
561
     */
562
    private function convertEncoding($str, $from = "ISO-8859-2", $to = "UTF-8") {
563
        if (function_exists('iconv') && $from != 'UTF-7' && $to != 'UTF-7') {
564
            return iconv($from, $to.'//IGNORE', $str);
565
        } else {
566
            if (!$from) {
567
                return mb_convert_encoding($str, $to);
568
            }
569
            return mb_convert_encoding($str, $to, $from);
570
        }
571
    }
572
573
    /**
574
     * Get the encoding of a given abject
575
     *
576
     * @param object $structure
577
     *
578
     * @return null|string
579
     */
580
    private function getEncoding($structure) {
581
        if (property_exists($structure, 'parameters')) {
582
            foreach ($structure->parameters as $parameter) {
583
                if (strtolower($parameter->attribute) == "charset") {
584
                    return strtoupper($parameter->value);
585
                }
586
            }
587
        }
588
        return null;
589
    }
590
591
    /**
592
     * Move the Message into an other Folder
593
     *
594
     * @param string  $mailbox
595
     * @param integer $options [optional]
596
     *
597
     * @return bool
598
     */
599
    public function moveToFolder($mailbox = 'INBOX', $options = 0) {
600
        $this->client->createFolder($mailbox);
601
602
        return imap_mail_move($this->client->getConnection(), $this->msglist, $mailbox, $options);
603
    }
604
605
    /**
606
     * Delete the current Message
607
     *
608
     * @return bool
609
     */
610
    public function delete() {
611
        $status = imap_delete($this->client->getConnection(), $this->uid, $this->fetch_options);
612
        $this->client->expunge();
613
614
        return $status;
615
    }
616
617
    /**
618
     * Restore a deleted Message
619
     *
620
     * @return bool
621
     */
622
    public function restore() {
623
        return imap_undelete($this->client->getConnection(), $this->message_no);
624
    }
625
626
    /**
627
     * Get all message attachments.
628
     *
629
     * @return AttachmentCollection
630
     */
631
    public function getAttachments() {
632
        return $this->attachments;
633
    }
634
635
    /**
636
     * Checks if there are any attachments present
637
     *
638
     * @return boolean
639
     */
640
    public function hasAttachments() {
641
        return $this->attachments->isEmpty() === false;
642
    }
643
644
    /**
645
     * Set a given flag
646
     * @param string|array $flag
647
     *
648
     * @return bool
649
     */
650
    public function setFlag($flag) {
651
        $flag = "\\".trim(is_array($flag) ? implode(" \\", $flag) : $flag);
652
        return imap_setflag_full($this->client->getConnection(), $this->getUid(), $flag, SE_UID);
653
    }
654
655
    /**
656
     * Unset a given flag
657
     * @param string|array $flag
658
     *
659
     * @return bool
660
     */
661
    public function unsetFlag($flag) {
662
        $flag = "\\".trim(is_array($flag) ? implode(" \\", $flag) : $flag);
663
        return imap_clearflag_full($this->client->getConnection(), $this->getUid(), "\\$flag", SE_UID);
664
    }
665
666
    /**
667
     * @return null|object|string
668
     */
669
    public function getRawBody() {
670
        if ($this->raw_body === null) {
671
            $this->raw_body = imap_fetchbody($this->client->getConnection(), $this->getMessageNo(), '');
0 ignored issues
show
Documentation Bug introduced by
It seems like imap_fetchbody($this->cl...is->getMessageNo(), '') of type string is incompatible with the declared type object|null of property $raw_body.

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...
672
        }
673
674
        return $this->raw_body;
675
    }
676
677
    /**
678
     * @return string
679
     */
680
    public function getHeader() {
681
        return $this->header;
682
    }
683
684
    /**
685
     * @return Client
686
     */
687
    public function getClient() {
688
        return $this->client;
689
    }
690
691
    /**
692
     * @return integer
693
     */
694
    public function getUid() {
695
        return $this->uid;
696
    }
697
698
    /**
699
     * @return integer
700
     */
701
    public function getFetchOptions() {
702
        return $this->fetch_options;
703
    }
704
705
    /**
706
     * @return boolean
707
     */
708
    public function getFetchBodyOption() {
709
        return $this->fetch_body;
710
    }
711
712
    /**
713
     * @return boolean
714
     */
715
    public function getFetchAttachmentOption() {
716
        return $this->fetch_attachment;
717
    }
718
719
    /**
720
     * @return int
721
     */
722
    public function getMsglist() {
723
        return $this->msglist;
724
    }
725
726
    /**
727
     * @return mixed
728
     */
729
    public function getMessageId() {
730
        return $this->message_id;
731
    }
732
733
    /**
734
     * @return int
735
     */
736
    public function getMessageNo() {
737
        return $this->message_no;
738
    }
739
740
    /**
741
     * @return string
742
     */
743
    public function getSubject() {
744
        return $this->subject;
745
    }
746
747
    /**
748
     * @return mixed
749
     */
750
    public function getReferences() {
751
        return $this->references;
752
    }
753
754
    /**
755
     * @return Carbon|null
756
     */
757
    public function getDate() {
758
        return $this->date;
759
    }
760
761
    /**
762
     * @return array
763
     */
764
    public function getFrom() {
765
        return $this->from;
766
    }
767
768
    /**
769
     * @return array
770
     */
771
    public function getTo() {
772
        return $this->to;
773
    }
774
775
    /**
776
     * @return array
777
     */
778
    public function getCc() {
779
        return $this->cc;
780
    }
781
782
    /**
783
     * @return array
784
     */
785
    public function getBcc() {
786
        return $this->bcc;
787
    }
788
789
    /**
790
     * @return array
791
     */
792
    public function getReplyTo() {
793
        return $this->reply_to;
794
    }
795
    
796
    /**
797
     * @return string
798
     */
799
    public function getInReplyTo() {
800
        return $this->in_reply_to;
801
    }
802
803
    /**
804
     * @return array
805
     */
806
    public function getSender() {
807
        return $this->sender;
808
    }
809
810
    /**
811
     * @return mixed
812
     */
813
    public function getBodies() {
814
        return $this->bodies;
815
    }
816
}
817