Passed
Push — master ( b0bac1...41e857 )
by Malte
03:36
created

Message::fetchAttachment()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 6
Bugs 1 Features 0
Metric Value
cc 3
eloc 6
c 6
b 1
f 0
nc 3
nop 2
dl 0
loc 9
rs 10
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 Illuminate\Support\Str;
17
use Webklex\IMAP\Events\MessageDeletedEvent;
18
use Webklex\IMAP\Events\MessageMovedEvent;
19
use Webklex\IMAP\Events\MessageRestoredEvent;
20
use Webklex\IMAP\Exceptions\InvalidMessageDateException;
21
use Webklex\IMAP\Exceptions\MaskNotFoundException;
22
use Webklex\IMAP\Exceptions\MethodNotFoundException;
23
use Webklex\IMAP\Support\AttachmentCollection;
24
use Webklex\IMAP\Support\FlagCollection;
25
use Webklex\IMAP\Support\Masks\MessageMask;
26
27
/**
28
 * Class Message
29
 *
30
 * @package Webklex\IMAP
31
 *
32
 * @property integer msglist
33
 * @property integer uid
34
 * @property integer msgn
35
 * @property integer priority
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
 * @property string fallback_encoding
49
 *
50
 * @method integer getMsglist()
51
 * @method integer setMsglist(integer $msglist)
52
 * @method integer getUid()
53
 * @method integer setUid(integer $uid)
54
 * @method integer getMsgn()
55
 * @method integer setMsgn(integer $msgn)
56
 * @method integer getPriority()
57
 * @method integer setPriority(integer $priority)
58
 * @method string getSubject()
59
 * @method string setSubject(string $subject)
60
 * @method string getMessageId()
61
 * @method string setMessageId(string $message_id)
62
 * @method string getMessageNo()
63
 * @method string setMessageNo(string $message_no)
64
 * @method string getReferences()
65
 * @method string setReferences(string $references)
66
 * @method carbon getDate()
67
 * @method carbon setDate(carbon $date)
68
 * @method array getFrom()
69
 * @method array setFrom(array $from)
70
 * @method array getTo()
71
 * @method array setTo(array $to)
72
 * @method array getCc()
73
 * @method array setCc(array $cc)
74
 * @method array getBcc()
75
 * @method array setBcc(array $bcc)
76
 * @method array getReplyTo()
77
 * @method array setReplyTo(array $reply_to)
78
 * @method array getInReplyTo()
79
 * @method array setInReplyTo(array $in_reply_to)
80
 * @method array getSender()
81
 * @method array setSender(array $sender)
82
 */
83
class Message {
84
85
    /**
86
     * Client instance
87
     *
88
     * @var Client
89
     */
90
    private $client = Client::class;
91
92
    /**
93
     * Default mask
94
     * @var string $mask
95
     */
96
    protected $mask = MessageMask::class;
97
98
    /** @var array $config */
99
    protected $config = [];
100
101
    /** @var array $attributes */
102
    protected $attributes = [
103
        'message_id' => '',
104
        'message_no' => null,
105
        'subject' => '',
106
        'references' => null,
107
        'date' => null,
108
        'from' => [],
109
        'to' => [],
110
        'cc' => [],
111
        'bcc' => [],
112
        'reply_to' => [],
113
        'in_reply_to' => '',
114
        'sender' => [],
115
        'priority' => 0,
116
    ];
117
118
    /**
119
     * The message folder path
120
     *
121
     * @var string $folder_path
122
     */
123
    protected $folder_path;
124
125
    /**
126
     * Fetch body options
127
     *
128
     * @var integer
129
     */
130
    public $fetch_options = null;
131
132
    /**
133
     * Fetch body options
134
     *
135
     * @var bool
136
     */
137
    public $fetch_body = null;
138
139
    /**
140
     * Fetch attachments options
141
     *
142
     * @var bool
143
     */
144
    public $fetch_attachment = null;
145
146
    /**
147
     * Fetch flags options
148
     *
149
     * @var bool
150
     */
151
    public $fetch_flags = null;
152
153
    /**
154
     * @var string $header
155
     */
156
    public $header = null;
157
158
    /**
159
     * @var null|object $header_info
160
     */
161
    public $header_info = null;
162
163
    /** @var null|string $raw_body */
164
    public $raw_body = null;
165
166
    /** @var null $structure */
167
    protected $structure = null;
168
169
    /**
170
     * Message body components
171
     *
172
     * @var array   $bodies
173
     * @var AttachmentCollection|array $attachments
174
     * @var FlagCollection|array       $flags
175
     */
176
    public $bodies = [];
177
    public $attachments = [];
178
    public $flags = [];
179
180
    /**
181
     * Fallback Encoding
182
     * @var string
183
     */
184
    public $fallback_encoding = 'UTF-8';
185
186
    /**
187
     * A list of all available and supported flags
188
     *
189
     * @var array $available_flags
190
     */
191
    private $available_flags = ['recent', 'flagged', 'answered', 'deleted', 'seen', 'draft'];
192
193
    /**
194
     * Message constructor.
195
     *
196
     * @param integer       $uid
197
     * @param integer|null  $msglist
198
     * @param Client        $client
199
     * @param integer|null  $fetch_options
200
     * @param boolean       $fetch_body
201
     * @param boolean       $fetch_attachment
202
     * @param boolean       $fetch_flags
203
     *
204
     * @throws Exceptions\ConnectionFailedException
205
     * @throws InvalidMessageDateException
206
     */
207
    public function __construct($uid, $msglist, Client $client, $fetch_options = null, $fetch_body = null, $fetch_attachment = null, $fetch_flags = null) {
208
209
        $default_mask = $client->getDefaultMessageMask();
210
        if($default_mask != null) {
211
            $this->mask = $default_mask;
212
        }
213
214
        $this->folder_path = $client->getFolderPath();
0 ignored issues
show
Documentation Bug introduced by
It seems like $client->getFolderPath() of type Webklex\IMAP\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...
215
216
        $this->config = config('imap.options');
217
218
        $this->setFetchOption($fetch_options);
219
        $this->setFetchBodyOption($fetch_body);
220
        $this->setFetchAttachmentOption($fetch_attachment);
221
        $this->setFetchFlagsOption($fetch_flags);
222
223
        $this->attachments = AttachmentCollection::make([]);
224
        $this->flags = FlagCollection::make([]);
225
226
        $this->msglist = $msglist;
227
        $this->client = $client;
228
229
        $this->uid =  $uid;
230
        $this->msgn = ($this->fetch_options == IMAP::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

230
        $this->msgn = ($this->fetch_options == IMAP::FT_UID) ? \imap_msgno(/** @scrutinizer ignore-type */ $this->client->getConnection(), $uid) : $uid;
Loading history...
231
232
        $this->parseHeader();
233
234
        if ($this->getFetchFlagsOption() === true) {
235
            $this->parseFlags();
236
        }
237
238
        if ($this->getFetchBodyOption() === true) {
239
            $this->parseBody();
240
        }
241
    }
242
243
    /**
244
     * Call dynamic attribute setter and getter methods
245
     * @param string $method
246
     * @param array $arguments
247
     *
248
     * @return mixed
249
     * @throws MethodNotFoundException
250
     */
251
    public function __call($method, $arguments) {
252
        if(strtolower(substr($method, 0, 3)) === 'get') {
253
            $name = Str::snake(substr($method, 3));
254
255
            if(in_array($name, array_keys($this->attributes))) {
256
                return $this->attributes[$name];
257
            }
258
259
        }elseif (strtolower(substr($method, 0, 3)) === 'set') {
260
            $name = Str::snake(substr($method, 3));
261
262
            if(in_array($name, array_keys($this->attributes))) {
263
                $this->attributes[$name] = array_pop($arguments);
264
265
                return $this->attributes[$name];
266
            }
267
268
        }
269
270
        throw new MethodNotFoundException("Method ".self::class.'::'.$method.'() is not supported');
271
    }
272
273
    /**
274
     * @param $name
275
     * @param $value
276
     *
277
     * @return mixed
278
     */
279
    public function __set($name, $value) {
280
        $this->attributes[$name] = $value;
281
282
        return $this->attributes[$name];
283
    }
284
285
    /**
286
     * @param $name
287
     *
288
     * @return mixed|null
289
     */
290
    public function __get($name) {
291
        if(isset($this->attributes[$name])) {
292
            return $this->attributes[$name];
293
        }
294
295
        return null;
296
    }
297
298
    /**
299
     * Copy the current Messages to a mailbox
300
     *
301
     * @param $mailbox
302
     * @param int $options
303
     *
304
     * @return bool
305
     * @throws Exceptions\ConnectionFailedException
306
     */
307
    public function copy($mailbox, $options = 0) {
0 ignored issues
show
Unused Code introduced by
The parameter $options is not used and could be removed. ( Ignorable by Annotation )

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

307
    public function copy($mailbox, /** @scrutinizer ignore-unused */ $options = 0) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
308
        $this->client->openFolder($this->folder_path);
309
        return \imap_mail_copy($this->client->getConnection(), $this->uid, $mailbox, IMAP::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_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

309
        return \imap_mail_copy(/** @scrutinizer ignore-type */ $this->client->getConnection(), $this->uid, $mailbox, IMAP::CP_UID);
Loading history...
310
    }
311
312
    /**
313
     * Move the current Messages to a mailbox
314
     *
315
     * @param $mailbox
316
     * @param int $options
317
     *
318
     * @return bool
319
     * @throws Exceptions\ConnectionFailedException
320
     */
321
    public function move($mailbox, $options = 0) {
0 ignored issues
show
Unused Code introduced by
The parameter $options is not used and could be removed. ( Ignorable by Annotation )

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

321
    public function move($mailbox, /** @scrutinizer ignore-unused */ $options = 0) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
322
        $this->client->openFolder($this->folder_path);
323
        return \imap_mail_move($this->client->getConnection(), $this->uid, $mailbox, IMAP::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

323
        return \imap_mail_move(/** @scrutinizer ignore-type */ $this->client->getConnection(), $this->uid, $mailbox, IMAP::CP_UID);
Loading history...
324
    }
325
326
    /**
327
     * Check if the Message has a text body
328
     *
329
     * @return bool
330
     */
331
    public function hasTextBody() {
332
        return isset($this->bodies['text']);
333
    }
334
335
    /**
336
     * Get the Message text body
337
     *
338
     * @return mixed
339
     */
340
    public function getTextBody() {
341
        if (!isset($this->bodies['text'])) {
342
            return false;
343
        }
344
345
        return $this->bodies['text']->content;
346
    }
347
348
    /**
349
     * Check if the Message has a html body
350
     *
351
     * @return bool
352
     */
353
    public function hasHTMLBody() {
354
        return isset($this->bodies['html']);
355
    }
356
357
    /**
358
     * Get the Message html body
359
     *
360
     * @return string|null
361
     */
362
    public function getHTMLBody() {
363
        if (!isset($this->bodies['html'])) {
364
            return null;
365
        }
366
        return $this->bodies['html']->content;
367
    }
368
369
    /**
370
     * Parse all defined headers
371
     *
372
     * @return void
373
     * @throws Exceptions\ConnectionFailedException
374
     * @throws InvalidMessageDateException
375
     */
376
    private function parseHeader() {
377
        $this->client->openFolder($this->folder_path);
378
        $this->header = $header = \imap_fetchheader($this->client->getConnection(), $this->uid, IMAP::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

378
        $this->header = $header = \imap_fetchheader(/** @scrutinizer ignore-type */ $this->client->getConnection(), $this->uid, IMAP::FT_UID);
Loading history...
379
380
        $this->priority = $this->extractPriority($this->header);
381
382
        if ($this->header) {
383
            $header = \imap_rfc822_parse_headers($this->header);
384
        }
385
386
        if (property_exists($header, 'subject')) {
387
            if($this->config['decoder']['message']['subject'] === 'utf-8') {
388
                $this->subject = \imap_utf8($header->subject);
389
            }elseif($this->config['decoder']['message']['subject'] === 'iconv') {
390
                $this->subject = iconv_mime_decode($header->subject);
391
            }else{
392
                $this->subject = mb_decode_mimeheader($header->subject);
393
            }
394
        }
395
396
        foreach(['from', 'to', 'cc', 'bcc', 'reply_to', 'sender'] as $part){
397
            $this->extractHeaderAddressPart($header, $part);
0 ignored issues
show
Bug introduced by
It seems like $header can also be of type string; however, parameter $header of Webklex\IMAP\Message::extractHeaderAddressPart() does only seem to accept object, 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

397
            $this->extractHeaderAddressPart(/** @scrutinizer ignore-type */ $header, $part);
Loading history...
398
        }
399
400
        if (property_exists($header, 'references')) {
401
            $this->references = $header->references;
402
        }
403
        if (property_exists($header, 'in_reply_to')) {
404
            $this->in_reply_to = str_replace(['<', '>'], '', $header->in_reply_to);
405
        }
406
        if (property_exists($header, 'message_id')) {
407
            $this->message_id = str_replace(['<', '>'], '', $header->message_id);
408
        }
409
        if (property_exists($header, 'Msgno')) {
410
            $messageNo = (int) trim($header->Msgno);
411
            $this->message_no = ($this->fetch_options == IMAP::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

411
            $this->message_no = ($this->fetch_options == IMAP::FT_UID) ? $messageNo : \imap_msgno(/** @scrutinizer ignore-type */ $this->client->getConnection(), $messageNo);
Loading history...
412
        } else {
413
            $this->message_no = \imap_msgno($this->client->getConnection(), $this->getUid());
414
        }
415
416
        $this->date = $this->parseDate($header);
0 ignored issues
show
Bug introduced by
It seems like $header can also be of type string; however, parameter $header of Webklex\IMAP\Message::parseDate() does only seem to accept object, 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

416
        $this->date = $this->parseDate(/** @scrutinizer ignore-type */ $header);
Loading history...
417
    }
418
419
    /**
420
     * Try to extract the priority from a given raw header string
421
     * @param string $header
422
     *
423
     * @return int|null
424
     */
425
    private function extractPriority($header) {
426
        if(preg_match('/x\-priority\:.*([0-9]{1,2})/i', $header, $priority)){
427
            $priority = isset($priority[1]) ? (int) $priority[1] : 0;
428
            switch($priority){
429
                case IMAP::MESSAGE_PRIORITY_HIGHEST;
430
                    $priority = IMAP::MESSAGE_PRIORITY_HIGHEST;
431
                    break;
432
                case IMAP::MESSAGE_PRIORITY_HIGH;
433
                    $priority = IMAP::MESSAGE_PRIORITY_HIGH;
434
                    break;
435
                case IMAP::MESSAGE_PRIORITY_NORMAL;
436
                    $priority = IMAP::MESSAGE_PRIORITY_NORMAL;
437
                    break;
438
                case IMAP::MESSAGE_PRIORITY_LOW;
439
                    $priority = IMAP::MESSAGE_PRIORITY_LOW;
440
                    break;
441
                case IMAP::MESSAGE_PRIORITY_LOWEST;
442
                    $priority = IMAP::MESSAGE_PRIORITY_LOWEST;
443
                    break;
444
                default:
445
                    $priority = IMAP::MESSAGE_PRIORITY_UNKNOWN;
446
                    break;
447
            }
448
        }
449
450
        return $priority;
451
    }
452
453
    /**
454
     * Exception handling for invalid dates
455
     *
456
     * Currently known invalid formats:
457
     * ^ Datetime                                   ^ Problem                           ^ Cause
458
     * | Mon, 20 Nov 2017 20:31:31 +0800 (GMT+8:00) | Double timezone specification     | A Windows feature
459
     * | Thu, 8 Nov 2018 08:54:58 -0200 (-02)       |
460
     * |                                            | and invalid timezone (max 6 char) |
461
     * | 04 Jan 2018 10:12:47 UT                    | Missing letter "C"                | Unknown
462
     * | Thu, 31 May 2018 18:15:00 +0800 (added by) | Non-standard details added by the | Unknown
463
     * |                                            | mail server                       |
464
     * | Sat, 31 Aug 2013 20:08:23 +0580            | Invalid timezone                  | PHPMailer bug https://sourceforge.net/p/phpmailer/mailman/message/6132703/
465
     *
466
     * Please report any new invalid timestamps to [#45](https://github.com/Webklex/laravel-imap/issues/45)
467
     *
468
     * @param object $header
469
     *
470
     * @return Carbon|null
471
     * @throws InvalidMessageDateException
472
     */
473
    private function parseDate($header) {
474
        $parsed_date = null;
475
476
        if (property_exists($header, 'date')) {
477
            $date = $header->date;
478
479
            if(preg_match('/\+0580/', $date)) {
480
                $date = str_replace('+0580', '+0530', $date);
481
            }
482
483
            $date = trim(rtrim($date));
484
            try {
485
                $parsed_date = Carbon::parse($date);
486
            } catch (\Exception $e) {
487
                switch (true) {
488
                    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:
489
                    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}\ UT)+$/i', $date) > 0:
490
                        $date .= 'C';
491
                        break;
492
                    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]{2,4}\ \(\+[0-9]{1,2}\))+$/i', $date) > 0:
493
                    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}.*)+$/i', $date) > 0:
494
                    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:
495
                    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:
496
                    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:
497
                        $array = explode('(', $date);
498
                        $array = array_reverse($array);
499
                        $date = trim(array_pop($array));
500
                        break;
501
                }
502
                try{
503
                    $parsed_date = Carbon::parse($date);
504
                } catch (\Exception $_e) {
505
                    throw new InvalidMessageDateException("Invalid message date. ID:".$this->getMessageId(), 1000, $e);
506
                }
507
            }
508
        }
509
510
        return $parsed_date;
511
    }
512
513
    /**
514
     * Parse additional flags
515
     *
516
     * @return void
517
     * @throws Exceptions\ConnectionFailedException
518
     */
519
    private function parseFlags() {
520
        $this->flags = FlagCollection::make([]);
521
522
        $this->client->openFolder($this->folder_path);
523
        $flags = \imap_fetch_overview($this->client->getConnection(), $this->uid, IMAP::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

523
        $flags = \imap_fetch_overview(/** @scrutinizer ignore-type */ $this->client->getConnection(), $this->uid, IMAP::FT_UID);
Loading history...
524
        if (is_array($flags) && isset($flags[0])) {
525
            foreach($this->available_flags as $flag) {
526
                $this->parseFlag($flags, $flag);
527
            }
528
        }
529
    }
530
531
    /**
532
     * Extract a possible flag information from a given array
533
     * @param array $flags
534
     * @param string $flag
535
     */
536
    private function parseFlag($flags, $flag) {
537
        $flag = strtolower($flag);
538
539
        if (property_exists($flags[0], strtoupper($flag))) {
540
            $this->flags->put($flag, $flags[0]->{strtoupper($flag)});
541
        } elseif (property_exists($flags[0], ucfirst($flag))) {
542
            $this->flags->put($flag, $flags[0]->{ucfirst($flag)});
543
        } elseif (property_exists($flags[0], $flag)) {
544
            $this->flags->put($flag, $flags[0]->$flag);
545
        }
546
    }
547
548
    /**
549
     * Get the current Message header info
550
     *
551
     * @return object
552
     * @throws Exceptions\ConnectionFailedException
553
     */
554
    public function getHeaderInfo() {
555
        if ($this->header_info == null) {
556
            $this->client->openFolder($this->folder_path);
557
            $this->header_info = \imap_headerinfo($this->client->getConnection(), $this->getMessageNo());
0 ignored issues
show
Bug introduced by
$this->getMessageNo() of type string is incompatible with the type integer expected by parameter $msg_no of imap_headerinfo(). ( Ignorable by Annotation )

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

557
            $this->header_info = \imap_headerinfo($this->client->getConnection(), /** @scrutinizer ignore-type */ $this->getMessageNo());
Loading history...
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

557
            $this->header_info = \imap_headerinfo(/** @scrutinizer ignore-type */ $this->client->getConnection(), $this->getMessageNo());
Loading history...
558
        }
559
560
        return $this->header_info;
561
    }
562
563
    /**
564
     * Extract a given part as address array from a given header
565
     * @param object $header
566
     * @param string $part
567
     */
568
    private function extractHeaderAddressPart($header, $part) {
569
        if (property_exists($header, $part)) {
570
            $this->$part = $this->parseAddresses($header->$part);
571
        }
572
    }
573
574
    /**
575
     * Parse Addresses
576
     * @param $list
577
     *
578
     * @return array
579
     */
580
    private function parseAddresses($list) {
581
        $addresses = [];
582
583
        foreach ($list as $item) {
584
            $address = (object) $item;
585
586
            if (!property_exists($address, 'mailbox')) {
587
                $address->mailbox = false;
588
            }
589
            if (!property_exists($address, 'host')) {
590
                $address->host = false;
591
            }
592
            if (!property_exists($address, 'personal')) {
593
                $address->personal = false;
594
            } else {
595
                $personalParts = \imap_mime_header_decode($address->personal);
596
597
                if(is_array($personalParts)) {
598
                    $address->personal = '';
599
                    foreach ($personalParts as $p) {
600
                        $address->personal .= $this->convertEncoding($p->text, $this->getEncoding($p));
601
                    }
602
                }
603
            }
604
605
            $address->mail = ($address->mailbox && $address->host) ? $address->mailbox.'@'.$address->host : false;
606
            $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

606
            $address->full = ($address->personal) ? $address->personal.' <'./** @scrutinizer ignore-type */ $address->mail.'>' : $address->mail;
Loading history...
607
608
            $addresses[] = $address;
609
        }
610
611
        return $addresses;
612
    }
613
614
    /**
615
     * Parse the Message body
616
     *
617
     * @return $this
618
     * @throws Exceptions\ConnectionFailedException
619
     */
620
    public function parseBody() {
621
        $this->client->openFolder($this->folder_path);
622
623
        $this->structure = \imap_fetchstructure($this->client->getConnection(), $this->uid, IMAP::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

623
        $this->structure = \imap_fetchstructure(/** @scrutinizer ignore-type */ $this->client->getConnection(), $this->uid, IMAP::FT_UID);
Loading history...
Documentation Bug introduced by
It seems like imap_fetchstructure($thi...klex\IMAP\IMAP::FT_UID) of type object is incompatible with the declared type null of property $structure.

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...
624
        $this->fetchStructure($this->structure);
625
626
        return $this;
627
    }
628
629
    /**
630
     * Fetch the Message structure
631
     *
632
     * @param $structure
633
     * @param mixed $partNumber
634
     *
635
     * @throws Exceptions\ConnectionFailedException
636
     */
637
    private function fetchStructure($structure, $partNumber = null) {
638
        $this->client->openFolder($this->folder_path);
639
640
        if ($structure->type == IMAP::MESSAGE_TYPE_TEXT &&
641
            (empty($structure->disposition) || strtolower($structure->disposition) != 'attachment')
642
        ) {
643
            if (strtolower($structure->subtype) == "plain" || strtolower($structure->subtype) == "csv") {
644
                $this->bodies['text'] = $this->createBody("text", $structure, $partNumber);
645
                $this->fetchAttachment($structure, $partNumber);
646
            } elseif (strtolower($structure->subtype) == "html") {
647
                $this->bodies['html'] = $this->createBody("html", $structure, $partNumber);
648
            } elseif ($structure->ifdisposition == 1 && strtolower($structure->disposition) == 'attachment') {
649
                if ($this->getFetchAttachmentOption() === true) {
650
                    $this->fetchAttachment($structure, $partNumber);
651
                }
652
            }
653
        } elseif ($structure->type == IMAP::MESSAGE_TYPE_MULTIPART) {
654
            foreach ($structure->parts as $index => $subStruct) {
655
                $prefix = "";
656
                if ($partNumber) {
657
                    $prefix = $partNumber.".";
658
                }
659
                $this->fetchStructure($subStruct, $prefix.($index + 1));
660
            }
661
        } else if ($this->getFetchAttachmentOption() === true) {
662
            $this->fetchAttachment($structure, $partNumber);
663
        }
664
    }
665
666
    /**
667
     * Create a new body object of a given type
668
     * @param string $type
669
     * @param object $structure
670
     * @param mixed $partNumber
671
     *
672
     * @return object
673
     * @throws Exceptions\ConnectionFailedException
674
     */
675
    private function createBody($type, $structure, $partNumber){
676
        return (object) [
677
            "type" => $type,
678
            "content" => $this->fetchPart($structure, $partNumber),
679
        ];
680
    }
681
682
    /**
683
     * Fetch the content of a given part and message structure
684
     * @param object $structure
685
     * @param mixed $partNumber
686
     *
687
     * @return mixed|string
688
     * @throws Exceptions\ConnectionFailedException
689
     */
690
    private function fetchPart($structure, $partNumber){
691
        $encoding = $this->getEncoding($structure);
692
693
        $content = \imap_fetchbody($this->client->getConnection(), $this->uid, $partNumber | 1, $this->fetch_options | IMAP::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

693
        $content = \imap_fetchbody(/** @scrutinizer ignore-type */ $this->client->getConnection(), $this->uid, $partNumber | 1, $this->fetch_options | IMAP::FT_UID);
Loading history...
694
        $content = $this->decodeString($content, $structure->encoding);
695
696
        // We don't need to do convertEncoding() if charset is ASCII (us-ascii):
697
        //     ASCII is a subset of UTF-8, so all ASCII files are already UTF-8 encoded
698
        //     https://stackoverflow.com/a/11303410
699
        //
700
        // us-ascii is the same as ASCII:
701
        //     ASCII is the traditional name for the encoding system; the Internet Assigned Numbers Authority (IANA)
702
        //     prefers the updated name US-ASCII, which clarifies that this system was developed in the US and
703
        //     based on the typographical symbols predominantly in use there.
704
        //     https://en.wikipedia.org/wiki/ASCII
705
        //
706
        // convertEncoding() function basically means convertToUtf8(), so when we convert ASCII string into UTF-8 it gets broken.
707
        if ($encoding != 'us-ascii') {
708
            $content = $this->convertEncoding($content, $encoding);
709
        }
710
711
        return $content;
712
    }
713
714
    /**
715
     * Fetch the Message attachment
716
     *
717
     * @param object $structure
718
     * @param mixed  $partNumber
719
     *
720
     * @throws Exceptions\ConnectionFailedException
721
     */
722
    protected function fetchAttachment($structure, $partNumber) {
723
724
        $oAttachment = new Attachment($this, $structure, $partNumber);
725
726
        if ($oAttachment->getName() !== null) {
0 ignored issues
show
introduced by
The condition $oAttachment->getName() !== null is always true.
Loading history...
727
            if ($oAttachment->getId() !== null) {
0 ignored issues
show
introduced by
The condition $oAttachment->getId() !== null is always true.
Loading history...
728
                $this->attachments->put($oAttachment->getId(), $oAttachment);
729
            } else {
730
                $this->attachments->push($oAttachment);
731
            }
732
        }
733
    }
734
735
    /**
736
     * Fail proof setter for $fetch_option
737
     *
738
     * @param $option
739
     *
740
     * @return $this
741
     */
742
    public function setFetchOption($option) {
743
        if (is_long($option) === true) {
744
            $this->fetch_options = $option;
745
        } elseif (is_null($option) === true) {
746
            $config = config('imap.options.fetch', IMAP::FT_UID);
747
            $this->fetch_options = is_long($config) ? $config : 1;
748
        }
749
750
        return $this;
751
    }
752
753
    /**
754
     * Fail proof setter for $fetch_body
755
     *
756
     * @param $option
757
     *
758
     * @return $this
759
     */
760
    public function setFetchBodyOption($option) {
761
        if (is_bool($option)) {
762
            $this->fetch_body = $option;
763
        } elseif (is_null($option)) {
764
            $config = config('imap.options.fetch_body', true);
765
            $this->fetch_body = is_bool($config) ? $config : true;
766
        }
767
768
        return $this;
769
    }
770
771
    /**
772
     * Fail proof setter for $fetch_attachment
773
     *
774
     * @param $option
775
     *
776
     * @return $this
777
     */
778
    public function setFetchAttachmentOption($option) {
779
        if (is_bool($option)) {
780
            $this->fetch_attachment = $option;
781
        } elseif (is_null($option)) {
782
            $config = config('imap.options.fetch_attachment', true);
783
            $this->fetch_attachment = is_bool($config) ? $config : true;
784
        }
785
786
        return $this;
787
    }
788
789
    /**
790
     * Fail proof setter for $fetch_flags
791
     *
792
     * @param $option
793
     *
794
     * @return $this
795
     */
796
    public function setFetchFlagsOption($option) {
797
        if (is_bool($option)) {
798
            $this->fetch_flags = $option;
799
        } elseif (is_null($option)) {
800
            $config = config('imap.options.fetch_flags', true);
801
            $this->fetch_flags = is_bool($config) ? $config : true;
802
        }
803
804
        return $this;
805
    }
806
807
    /**
808
     * Decode a given string
809
     *
810
     * @param $string
811
     * @param $encoding
812
     *
813
     * @return string
814
     */
815
    public function decodeString($string, $encoding) {
816
        switch ($encoding) {
817
            case IMAP::MESSAGE_ENC_8BIT:
818
                return quoted_printable_decode(\imap_8bit($string));
819
            case IMAP::MESSAGE_ENC_BINARY:
820
                return \imap_binary($string);
821
            case IMAP::MESSAGE_ENC_BASE64:
822
                return \imap_base64($string);
823
            case IMAP::MESSAGE_ENC_QUOTED_PRINTABLE:
824
                return quoted_printable_decode($string);
825
            case IMAP::MESSAGE_ENC_7BIT:
826
            case IMAP::MESSAGE_ENC_OTHER:
827
            default:
828
                return $string;
829
        }
830
    }
831
832
    /**
833
     * Convert the encoding
834
     *
835
     * @param $str
836
     * @param string $from
837
     * @param string $to
838
     *
839
     * @return mixed|string
840
     */
841
    public function convertEncoding($str, $from = "ISO-8859-2", $to = "UTF-8") {
842
843
        $from = EncodingAliases::get($from, $this->fallback_encoding);
844
        $to = EncodingAliases::get($to, $this->fallback_encoding);
845
846
        if ($from === $to) {
847
            return $str;
848
        }
849
850
        // We don't need to do convertEncoding() if charset is ASCII (us-ascii):
851
        //     ASCII is a subset of UTF-8, so all ASCII files are already UTF-8 encoded
852
        //     https://stackoverflow.com/a/11303410
853
        //
854
        // us-ascii is the same as ASCII:
855
        //     ASCII is the traditional name for the encoding system; the Internet Assigned Numbers Authority (IANA)
856
        //     prefers the updated name US-ASCII, which clarifies that this system was developed in the US and
857
        //     based on the typographical symbols predominantly in use there.
858
        //     https://en.wikipedia.org/wiki/ASCII
859
        //
860
        // convertEncoding() function basically means convertToUtf8(), so when we convert ASCII string into UTF-8 it gets broken.
861
        if (strtolower($from) == 'us-ascii' && $to == 'UTF-8') {
862
            return $str;
863
        }
864
865
        if (function_exists('iconv') && $from != 'UTF-7' && $to != 'UTF-7') {
866
            return @iconv($from, $to.'//IGNORE', $str);
867
        } else {
868
            if (!$from) {
869
                return mb_convert_encoding($str, $to);
870
            }
871
            return mb_convert_encoding($str, $to, $from);
872
        }
873
    }
874
875
    /**
876
     * Get the encoding of a given abject
877
     *
878
     * @param object|string $structure
879
     *
880
     * @return string
881
     */
882
    public function getEncoding($structure) {
883
        if (property_exists($structure, 'parameters')) {
884
            foreach ($structure->parameters as $parameter) {
885
                if (strtolower($parameter->attribute) == "charset") {
886
                    return EncodingAliases::get($parameter->value, $this->fallback_encoding);
887
                }
888
            }
889
        }elseif (property_exists($structure, 'charset')) {
890
            return EncodingAliases::get($structure->charset, $this->fallback_encoding);
891
        }elseif (is_string($structure) === true){
892
            return mb_detect_encoding($structure);
893
        }
894
895
        return $this->fallback_encoding;
896
    }
897
898
    /**
899
     * Find the folder containing this message.
900
     * @param null|Folder $folder where to start searching from (top-level inbox by default)
901
     *
902
     * @return mixed|null|Folder
903
     * @throws Exceptions\ConnectionFailedException
904
     * @throws Exceptions\MailboxFetchingException
905
     * @throws InvalidMessageDateException
906
     * @throws MaskNotFoundException
907
     */
908
    public function getContainingFolder(Folder $folder = null) {
909
        $folder = $folder ?: $this->client->getFolders()->first();
910
        $this->client->checkConnection();
911
912
        // Try finding the message by uid in the current folder
913
        $client = new Client;
914
        $client->openFolder($folder->path);
915
        $uidMatches = \imap_fetch_overview($client->getConnection(), $this->uid, IMAP::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

915
        $uidMatches = \imap_fetch_overview(/** @scrutinizer ignore-type */ $client->getConnection(), $this->uid, IMAP::FT_UID);
Loading history...
916
        $uidMatch = count($uidMatches)
917
            ? new Message($uidMatches[0]->uid, $uidMatches[0]->msgno, $client)
918
            : null;
919
        $client->disconnect();
920
921
        // \imap_fetch_overview() on a parent folder will return the matching message
922
        // even when the message is in a child folder so we need to recursively
923
        // search the children
924
        foreach ($folder->children as $child) {
925
            $childFolder = $this->getContainingFolder($child);
926
927
            if ($childFolder) {
928
                return $childFolder;
929
            }
930
        }
931
932
        // before returning the parent
933
        if ($this->is($uidMatch)) {
934
            return $folder;
935
        }
936
937
        // or signalling that the message was not found in any folder
938
        return null;
939
    }
940
941
    public function getFolder(){
942
        return $this->client->getFolder($this->folder_path);
943
    }
944
945
    /**
946
     * Move the Message into an other Folder
947
     * @param string $mailbox
948
     * @param bool $expunge
949
     * @param bool $create_folder
950
     *
951
     * @return null|Message
952
     * @throws Exceptions\ConnectionFailedException
953
     * @throws InvalidMessageDateException
954
     */
955
    public function moveToFolder($mailbox = 'INBOX', $expunge = false, $create_folder = true) {
956
957
        if($create_folder) $this->client->createFolder($mailbox, true);
958
959
        $target_folder = $this->client->getFolder($mailbox);
960
        $target_status = $target_folder->getStatus(IMAP::SA_ALL);
961
962
        $this->client->openFolder($this->folder_path);
963
        $status = \imap_mail_move($this->client->getConnection(), $this->uid, $mailbox, IMAP::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

963
        $status = \imap_mail_move(/** @scrutinizer ignore-type */ $this->client->getConnection(), $this->uid, $mailbox, IMAP::CP_UID);
Loading history...
964
965
        if($status === true){
966
            if($expunge) $this->client->expunge();
967
            $this->client->openFolder($target_folder->path);
968
969
            $message = $target_folder->getMessage($target_status->uidnext, null, $this->fetch_options, $this->fetch_body, $this->fetch_attachment, $this->fetch_flags);
970
            MessageMovedEvent::dispatch($this, $message);
971
            return $message;
972
        }
973
974
        return null;
975
    }
976
977
    /**
978
     * Delete the current Message
979
     * @param bool $expunge
980
     *
981
     * @return bool
982
     * @throws Exceptions\ConnectionFailedException
983
     */
984
    public function delete($expunge = true) {
985
        $this->client->openFolder($this->folder_path);
986
987
        $status = \imap_delete($this->client->getConnection(), $this->uid, IMAP::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

987
        $status = \imap_delete(/** @scrutinizer ignore-type */ $this->client->getConnection(), $this->uid, IMAP::FT_UID);
Loading history...
988
        if($expunge) $this->client->expunge();
989
        MessageDeletedEvent::dispatch($this);
990
991
        return $status;
992
    }
993
994
    /**
995
     * Restore a deleted Message
996
     * @param boolean $expunge
997
     *
998
     * @return bool
999
     * @throws Exceptions\ConnectionFailedException
1000
     */
1001
    public function restore($expunge = true) {
1002
        $this->client->openFolder($this->folder_path);
1003
1004
        $status = \imap_undelete($this->client->getConnection(), $this->uid, IMAP::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

1004
        $status = \imap_undelete(/** @scrutinizer ignore-type */ $this->client->getConnection(), $this->uid, IMAP::FT_UID);
Loading history...
1005
        if($expunge) $this->client->expunge();
1006
        MessageRestoredEvent::dispatch($this);
1007
1008
        return $status;
1009
    }
1010
1011
    /**
1012
     * Get all message attachments.
1013
     *
1014
     * @return AttachmentCollection
1015
     */
1016
    public function getAttachments() {
1017
        return $this->attachments;
1018
    }
1019
1020
    /**
1021
     * Checks if there are any attachments present
1022
     *
1023
     * @return boolean
1024
     */
1025
    public function hasAttachments() {
1026
        return $this->attachments->isEmpty() === false;
1027
    }
1028
1029
    /**
1030
     * Set a given flag
1031
     * @param string|array $flag
1032
     *
1033
     * @return bool
1034
     * @throws Exceptions\ConnectionFailedException
1035
     */
1036
    public function setFlag($flag) {
1037
        $this->client->openFolder($this->folder_path);
1038
1039
        $flag = "\\".trim(is_array($flag) ? implode(" \\", $flag) : $flag);
1040
        $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

1040
        $status = \imap_setflag_full(/** @scrutinizer ignore-type */ $this->client->getConnection(), $this->getUid(), $flag, SE_UID);
Loading history...
1041
        $this->parseFlags();
1042
1043
        return $status;
1044
    }
1045
1046
    /**
1047
     * Unset a given flag
1048
     * @param string|array $flag
1049
     *
1050
     * @return bool
1051
     * @throws Exceptions\ConnectionFailedException
1052
     */
1053
    public function unsetFlag($flag) {
1054
        $this->client->openFolder($this->folder_path);
1055
1056
        $flag = "\\".trim(is_array($flag) ? implode(" \\", $flag) : $flag);
1057
        $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

1057
        $status = \imap_clearflag_full(/** @scrutinizer ignore-type */ $this->client->getConnection(), $this->getUid(), $flag, SE_UID);
Loading history...
1058
        $this->parseFlags();
1059
1060
        return $status;
1061
    }
1062
1063
    /**
1064
     * @return null|object|string
1065
     * @throws Exceptions\ConnectionFailedException
1066
     */
1067
    public function getRawBody() {
1068
        if ($this->raw_body === null) {
1069
            $this->client->openFolder($this->folder_path);
1070
1071
            $this->raw_body = \imap_fetchbody($this->client->getConnection(), $this->getUid(), '', $this->fetch_options | IMAP::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

1071
            $this->raw_body = \imap_fetchbody(/** @scrutinizer ignore-type */ $this->client->getConnection(), $this->getUid(), '', $this->fetch_options | IMAP::FT_UID);
Loading history...
1072
        }
1073
1074
        return $this->raw_body;
1075
    }
1076
1077
    /**
1078
     * Get an almost unique message token
1079
     * @return string
1080
     * @throws Exceptions\ConnectionFailedException
1081
     */
1082
    public function getToken(){
1083
        return base64_encode(implode('-', [$this->message_id, $this->subject, strlen($this->getRawBody())]));
1084
    }
1085
1086
    /**
1087
     * @return string
1088
     */
1089
    public function getHeader() {
1090
        return $this->header;
1091
    }
1092
1093
    /**
1094
     * @return Client
1095
     */
1096
    public function getClient() {
1097
        return $this->client;
1098
    }
1099
1100
    /**
1101
     * @return integer
1102
     */
1103
    public function getFetchOptions() {
1104
        return $this->fetch_options;
1105
    }
1106
1107
    /**
1108
     * @return boolean
1109
     */
1110
    public function getFetchBodyOption() {
1111
        return $this->fetch_body;
1112
    }
1113
1114
    /**
1115
     * @return boolean
1116
     */
1117
    public function getFetchAttachmentOption() {
1118
        return $this->fetch_attachment;
1119
    }
1120
1121
    /**
1122
     * @return boolean
1123
     */
1124
    public function getFetchFlagsOption() {
1125
        return $this->fetch_flags;
1126
    }
1127
1128
    /**
1129
     * @return mixed
1130
     */
1131
    public function getBodies() {
1132
        return $this->bodies;
1133
    }
1134
1135
    /**
1136
     * @return FlagCollection
1137
     */
1138
    public function getFlags() {
1139
        return $this->flags;
1140
    }
1141
1142
    /**
1143
     * @return object|null
1144
     */
1145
    public function getStructure(){
1146
        return $this->structure;
1147
    }
1148
1149
    /**
1150
     * Does this message match another one?
1151
     *
1152
     * A match means same uid, message id, subject, body length and date/time.
1153
     *
1154
     * @param  null|Message $message
1155
     * @return bool
1156
     * @throws Exceptions\ConnectionFailedException
1157
     */
1158
    public function is(Message $message = null) {
1159
        if (is_null($message)) {
1160
            return false;
1161
        }
1162
1163
        return $this->getToken() == $message->getToken() && $this->date->eq($message->date);
1164
    }
1165
1166
    /**
1167
     * @return array
1168
     */
1169
    public function getAttributes(){
1170
        return $this->attributes;
1171
    }
1172
1173
    /**
1174
     * @param $mask
1175
     * @return $this
1176
     */
1177
    public function setMask($mask){
1178
        if(class_exists($mask)){
1179
            $this->mask = $mask;
1180
        }
1181
1182
        return $this;
1183
    }
1184
1185
    /**
1186
     * @return string
1187
     */
1188
    public function getMask(){
1189
        return $this->mask;
1190
    }
1191
1192
    /**
1193
     * Get a masked instance by providing a mask name
1194
     * @param string|null $mask
1195
     *
1196
     * @return mixed
1197
     * @throws MaskNotFoundException
1198
     */
1199
    public function mask($mask = null){
1200
        $mask = $mask !== null ? $mask : $this->mask;
1201
        if(class_exists($mask)){
1202
            return new $mask($this);
1203
        }
1204
1205
        throw new MaskNotFoundException("Unknown mask provided: ".$mask);
1206
    }
1207
}
1208