Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
2.92% covered (danger)
2.92%
4 / 137
10.26% covered (danger)
10.26%
4 / 39
CRAP
0.00% covered (danger)
0.00%
0 / 1
Imap
2.92% covered (danger)
2.92%
4 / 137
10.26% covered (danger)
10.26%
4 / 39
6539.83
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 connect
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
42
 open
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
72
 isOpen
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 connection
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getConnectionString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 listMailboxes
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getStatus
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getInfo
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getNumberOfMessages
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getNumberOfReadMessages
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getNumberOfUnreadMessages
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getOverview
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getMessageIds
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getMessageIdsBy
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getMessageHeaders
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 getMessageHeadersBy
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 getMessageNumber
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMessageHeaderInfoById
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 getMessageRawHeadersById
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
182
 getMessageHeadersById
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getMessageStructure
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMessageBoundary
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
30
 getMessageBody
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMessageParts
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getMessageAttachments
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 hasMessageAttachments
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 copyMessage
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 moveMessage
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 markAsRead
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 markAsUnread
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setMessageFlags
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 clearMessageFlags
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 deleteMessage
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 createMailbox
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 renameMailbox
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 deleteMailbox
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 decodeText
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 close
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2023 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Mail\Client;
15
16use Pop\Mail\Message;
17
18/**
19 * Mail client IMAP class
20 *
21 * @category   Pop
22 * @package    Pop\Mail
23 * @author     Nick Sagona, III <dev@nolainteractive.com>
24 * @copyright  Copyright (c) 2009-2023 NOLA Interactive, LLC. (http://www.nolainteractive.com)
25 * @license    http://www.popphp.org/license     New BSD License
26 * @version    3.6.0
27 */
28class Imap extends AbstractClient
29{
30
31    /**
32     * Mailbox connection string
33     * @var string
34     */
35    protected $connectionString = null;
36
37    /**
38     * Mailbox connection resource
39     * @var resource
40     */
41    protected $connection = null;
42
43    /**
44     * Constructor
45     *
46     * Instantiate the IMAP mail client object
47     *
48     * @param string $host
49     * @param int    $port
50     * @param string $service
51     */
52    public function __construct($host, $port, $service = 'imap')
53    {
54        parent::__construct($host, $port, $service);
55    }
56
57    /**
58     * Connect to an IMAP mailbox
59     *
60     * @param array  $creds
61     * @param string $flags
62     * @param int    $options
63     * @param int    $retries
64     * @param array  $params
65     * @return Imap
66     */
67    public static function connect(array $creds, $flags = null, $options = null, $retries = null, array $params = null)
68    {
69        if (!isset($creds['host']) || !isset($creds['port']) || !isset($creds['username']) || !isset($creds['password'])) {
70            throw new Exception(
71                "Error: The credentials were incomplete. They must contain 'host', 'port', 'username' and 'password'."
72            );
73        }
74
75        $imap = new static($creds['host'], $creds['port']);
76        $imap->setUsername($creds['username'])
77             ->setPassword($creds['password']);
78
79        if (isset($creds['folder'])) {
80            $imap->setFolder($creds['folder']);
81        }
82
83        return $imap->open($flags, $options, $retries, $params);
84    }
85
86    /**
87     * Open mailbox connection
88     *
89     * @param string $flags
90     * @param int    $options
91     * @param int    $retries
92     * @param array  $params
93     * @return Imap
94     */
95    public function open($flags = null, $options = null, $retries = null, array $params = null)
96    {
97        $this->connectionString = '{' . $this->host . ':' . $this->port . '/' . $this->service;
98
99        if (null !== $flags) {
100            $this->connectionString .= $flags;
101        }
102
103        $this->connectionString .= '}';
104
105        if ((null !== $options) && (null !== $retries) && (null !== $params)) {
106            $this->connection = imap_open(
107                $this->connectionString . $this->folder, $this->username, $this->password, $options, $retries, $params
108            );
109        } else if ((null !== $options) && (null !== $retries)) {
110            $this->connection = imap_open(
111                $this->connectionString . $this->folder, $this->username, $this->password, $options, $retries
112            );
113        } else if (null !== $options) {
114            $this->connection = imap_open(
115                $this->connectionString . $this->folder, $this->username, $this->password, $options
116            );
117        } else {
118            $this->connection = imap_open(
119                $this->connectionString . $this->folder, $this->username, $this->password
120            );
121        }
122
123        return $this;
124    }
125
126    /**
127     * Determine if the mailbox connection has been opened
128     *
129     * @return boolean
130     */
131    public function isOpen()
132    {
133        return is_resource($this->connection);
134    }
135
136    /**
137     * Get mailbox connection
138     *
139     * @return resource
140     */
141    public function connection()
142    {
143        return $this->connection;
144    }
145
146    /**
147     * Get mailbox connection string
148     *
149     * @return string
150     */
151    public function getConnectionString()
152    {
153        return $this->connectionString;
154    }
155
156    /**
157     * List mailboxes
158     *
159     * @param string $pattern
160     * @return array
161     */
162    public function listMailboxes($pattern = '*')
163    {
164        return imap_list($this->connection, $this->connectionString . $this->folder, $pattern);
165    }
166
167    /**
168     * Get mailbox status
169     *
170     * @return \stdClass
171     */
172    public function getStatus()
173    {
174        return imap_status($this->connection, $this->connectionString . $this->folder, SA_ALL);
175
176    }
177
178    /**
179     * Get mailbox info
180     *
181     * @return \stdClass
182     */
183    public function getInfo()
184    {
185        return imap_mailboxmsginfo($this->connection);
186
187    }
188    /**
189     * Get total number of messages
190     *
191     * @return int
192     */
193    public function getNumberOfMessages()
194    {
195        return imap_num_msg($this->connection);
196    }
197
198    /**
199     * Get total number of read messages
200     *
201     * @return int
202     */
203    public function getNumberOfReadMessages()
204    {
205        return abs($this->getNumberOfMessages() - $this->getStatus()->unseen);
206    }
207
208    /**
209     * Get total number of unread messages
210     *
211     * @return int
212     */
213    public function getNumberOfUnreadMessages()
214    {
215        return $this->getStatus()->unseen;
216    }
217
218    /**
219     * Get message overviews
220     *
221     * @param  mixed  $ids
222     * @param  int    $options
223     * @return array
224     */
225    public function getOverview($ids, $options = FT_UID)
226    {
227        if (is_array($ids)) {
228            $ids = implode(',', $ids);
229        }
230        return imap_fetch_overview($this->connection, $ids, $options);
231    }
232
233    /**
234     * Get message IDs from a mailbox
235     *
236     * @param string $criteria
237     * @param int    $options
238     * @param string $charset
239     * @return array
240     */
241    public function getMessageIds($criteria = 'ALL', $options = SE_UID, $charset = null)
242    {
243        return (null !== $charset) ?
244            imap_search($this->connection, $criteria, $options, $charset) :
245            imap_search($this->connection, $criteria, $options);
246    }
247
248    /**
249     * Get message IDs from a mailbox by a sort criteria
250     *
251     * @param  int     $criteria
252     * @param  boolean $reverse
253     * @param  int     $options
254     * @param  string  $search
255     * @param  string  $charset
256     * @return array
257     */
258    public function getMessageIdsBy($criteria = SORTDATE, $reverse = true, $options = SE_UID, $search = 'ALL', $charset = null)
259    {
260        return (null !== $charset) ?
261            imap_sort($this->connection, $criteria, (int)$reverse, $options, $search, $charset) :
262            imap_sort($this->connection, $criteria, (int)$reverse, $options, $search);
263    }
264
265    /**
266     * Get message headers from a mailbox
267     *
268     * @param  string $criteria
269     * @param  int    $options
270     * @param  string $charset
271     * @return array
272     */
273    public function getMessageHeaders($criteria = 'ALL', $options = SE_UID, $charset = null)
274    {
275        $headers = [];
276        $ids     = $this->getMessageIds($criteria, $options, $charset);
277
278        foreach ($ids as $id) {
279            $headers[$id] =  $this->getMessageHeadersById($id);
280        }
281
282        return $headers;
283    }
284
285    /**
286     * Get message headers from a mailbox
287     *
288     * @param  int     $criteria
289     * @param  boolean $reverse
290     * @param  int     $options
291     * @param  string  $search
292     * @param  string  $charset
293     * @return array
294     */
295    public function getMessageHeadersBy($criteria = SORTDATE, $reverse = true, $options = SE_UID, $search = 'ALL', $charset = null)
296    {
297        $headers = [];
298        $ids     = $this->getMessageIdsBy($criteria, $reverse, $options, $search, $charset);
299
300        foreach ($ids as $id) {
301            $headers[$id] =  $this->getMessageHeadersById($id);
302        }
303
304        return $headers;
305    }
306
307    /**
308     * Get message number from UID
309     *
310     * @param  int $id
311     * @return int
312     */
313    public function getMessageNumber($id)
314    {
315        return imap_msgno($this->connection, $id);
316    }
317
318    /**
319     * Get raw message headers by message ID
320     *
321     * @param  int $id
322     * @return array
323     */
324    public function getMessageHeaderInfoById($id)
325    {
326        $headers = imap_headerinfo($this->connection, imap_msgno($this->connection, $id));
327        return ($headers !== false) ? json_decode(json_encode($headers), true) : [];
328    }
329
330    /**
331     * Get raw message headers by message ID
332     *
333     * @param  int $id
334     * @return array
335     */
336    public function getMessageRawHeadersById($id)
337    {
338        $headers       = explode("\r\n", imap_fetchheader($this->connection, $id, FT_UID));
339        $parsedHeaders = [];
340        $name          = null;
341
342        foreach ($headers as $header) {
343            if (((substr($header, 0, 1) == ' ') || (substr($header, 0, 1) == "\t")) &&
344                (null !== $name) && isset($parsedHeaders[$name])) {
345                if (is_array($parsedHeaders[$name])) {
346                    $parsedHeaders[$name][key($parsedHeaders[$name])] .= $header;
347                } else {
348                    $parsedHeaders[$name] .= $header;
349                }
350            } else if (!empty($header) && (strpos($header, ':') !== false)) {
351                $name  = substr($header, 0, strpos($header, ':'));
352                $value = (strpos($header, ':') < (strlen($header) - 1)) ? substr($header, (strpos($header, ': ') + 2)) : '';
353
354                if (isset($parsedHeaders[$name])) {
355                    if (!is_array($parsedHeaders[$name])) {
356                        $parsedHeaders[$name] = [$parsedHeaders[$name]];
357                    }
358                    $parsedHeaders[$name][] = $value;
359                    end($parsedHeaders[$name]);
360                } else {
361                    $parsedHeaders[$name] = $value;
362                }
363            }
364        }
365
366        return array_map(function($value) {
367            if (is_array($value)) {
368                return array_map('trim', $value);
369            } else {
370                return trim($value);
371            }
372        }, $parsedHeaders);
373    }
374
375    /**
376     * Get message headers by message ID
377     *
378     * @param  int $id
379     * @return array
380     */
381    public function getMessageHeadersById($id)
382    {
383        $headers = imap_rfc822_parse_headers(imap_fetchheader($this->connection, $id, FT_UID));
384        return json_decode(json_encode($headers), true);
385    }
386
387    /**
388     * Get message structure by message ID
389     *
390     * @param  int $id
391     * @return \stdClass
392     */
393    public function getMessageStructure($id)
394    {
395        return imap_fetchstructure($this->connection, $id, FT_UID);
396    }
397
398    /**
399     * Get message boundary by message ID
400     *
401     * @param  int $id
402     * @return string
403     */
404    public function getMessageBoundary($id)
405    {
406        $boundary  = null;
407        $structure = $this->getMessageStructure($id);
408
409        if (isset($structure->parameters) && (count($structure->parameters) > 0)) {
410            foreach ($structure->parameters as $parameter) {
411                if (strtolower($parameter->attribute) == 'boundary') {
412                    $boundary = $parameter->value;
413                    break;
414                }
415            }
416        }
417
418        return $boundary;
419    }
420
421    /**
422     * Get message body by message ID
423     *
424     * @param  int $id
425     * @return string
426     */
427    public function getMessageBody($id)
428    {
429        return imap_body($this->connection, $id, FT_UID);
430    }
431
432    /**
433     * Get message parts by message ID
434     *
435     * @param  int $id
436     * @return array
437     */
438    public function getMessageParts($id)
439    {
440        $boundary = $this->getMessageBoundary($id);
441        $body     = $this->getMessageBody($id);
442        return Message\Part::parse($body, $boundary);
443    }
444
445    /**
446     * Get message parts by message ID
447     *
448     * @param  int    $id
449     * @param  string $encoding
450     * @return array
451     */
452    public function getMessageAttachments($id, $encoding = null)
453    {
454        return array_filter($this->getMessageParts($id, $encoding), function($part){
455            return $part->attachment;
456        });
457    }
458
459    /**
460     * Get message parts by message ID
461     *
462     * @param  int    $id
463     * @param  string $encoding
464     * @return boolean
465     */
466    public function hasMessageAttachments($id, $encoding = null)
467    {
468        return (count($this->getMessageAttachments($id, $encoding)) > 0);
469    }
470
471    /**
472     * Copy messages to another mailbox
473     *
474     * @param  mixed        $ids
475     * @param  string|array $to
476     * @param  int          $options
477     * @return Imap
478     */
479    public function copyMessage($ids, $to, $options = CP_UID)
480    {
481        if (is_array($ids)) {
482            $ids = implode(',', $ids);
483        }
484
485        imap_mail_copy($this->connection, $ids, $to, $options);
486        return $this;
487    }
488
489    /**
490     * Move messages to another mailbox
491     *
492     * @param  mixed        $ids
493     * @param  string|array $to
494     * @param  int          $options
495     * @return Imap
496     */
497    public function moveMessage($ids, $to, $options = CP_UID)
498    {
499        if (is_array($ids)) {
500            $ids = implode(',', $ids);
501        }
502
503        imap_mail_move($this->connection, $ids, $to, $options);
504        return $this;
505    }
506
507    /**
508     * Mark a message or messages as read
509     *
510     * @param  mixed $ids
511     * @param  int   $options
512     * @return Imap
513     */
514    public function markAsRead($ids, $options = ST_UID)
515    {
516        return $this->setMessageFlags($ids, "\\Seen", $options);
517    }
518
519    /**
520     * Mark a message or messages as unread
521     *
522     * @param  mixed $ids
523     * @param  int   $options
524     * @return Imap
525     */
526    public function markAsUnread($ids, $options = ST_UID)
527    {
528        return $this->clearMessageFlags($ids, "\\Seen", $options);
529    }
530
531    /**
532     * Mark a message or messages as read
533     *
534     * @param  mixed  $ids
535     * @param  string $flags
536     * @param  int    $options
537     * @return Imap
538     */
539    public function setMessageFlags($ids, $flags, $options = ST_UID)
540    {
541        if (is_array($ids)) {
542            $ids = implode(',', $ids);
543        }
544        imap_setflag_full($this->connection, $ids, $flags, $options);
545
546        return $this;
547    }
548
549    /**
550     * Mark a message or messages as unread
551     *
552     * @param  mixed  $ids
553     * @param  string $flags
554     * @param  int    $options
555     * @return Imap
556     */
557    public function clearMessageFlags($ids, $flags, $options = ST_UID)
558    {
559        if (is_array($ids)) {
560            $ids = implode(',', $ids);
561        }
562        imap_clearflag_full($this->connection, $ids, $flags, $options);
563
564        return $this;
565    }
566
567    /**
568     * Delete message
569     *
570     * @param  int $id
571     * @param  int $options
572     * @return Imap
573     */
574    public function deleteMessage($id, $options = FT_UID)
575    {
576        imap_delete($this->connection, $id, $options);
577        return $this;
578    }
579
580    /**
581     * Create mailbox
582     *
583     * @param  string $new
584     * @return Imap
585     */
586    public function createMailbox($new)
587    {
588        if (strpos($new, $this->connectionString) === false) {
589            $new = $this->connectionString . $new;
590        }
591        imap_createmailbox($this->connection, $new);
592        return $this;
593    }
594
595    /**
596     * Rename mailbox
597     *
598     * @param  string $new
599     * @param  string $old
600     * @return Imap
601     */
602    public function renameMailbox($new, $old = null)
603    {
604        if (null === $old) {
605            $old = $this->connectionString . $this->folder;
606        } else if (strpos($old, $this->connectionString) === false) {
607            $old = $this->connectionString . $old;
608        }
609
610        if (strpos($new, $this->connectionString) === false) {
611            $new = $this->connectionString . $new;
612        }
613
614        imap_renamemailbox($this->connection, $old, $new);
615        return $this;
616    }
617
618    /**
619     * Delete mailbox
620     *
621     * @param  string $mailbox
622     * @throws Exception
623     * @return Imap
624     */
625    public function deleteMailbox($mailbox = null)
626    {
627        if (null === $mailbox) {
628            $mailbox = $this->folder;
629        }
630
631        if (empty($mailbox)) {
632            throw new Exception('Error: The mailbox is not set.');
633        }
634
635        imap_deletemailbox($this->connection, $this->connectionString . $mailbox);
636        return $this;
637    }
638
639    /**
640     * Decode text
641     *
642     * @param  string $text
643     * @return string
644     */
645    public  function decodeText($text)
646    {
647        return Message::decodeText($text);
648    }
649
650    /**
651     * Close the mailbox connection resource
652     *
653     * @return void
654     */
655    public function close()
656    {
657        if (is_resource($this->connection)) {
658            imap_close($this->connection);
659        }
660    }
661
662}