php imap – get body and make plain text

I am using the PHP imap function to get emails from a POP3 mailbox and insert the data into a MySQL database.

Here is the PHP code:

$inbox = imap_open($hostname,$username,$password) or die('Cannot connect: ' . imap_last_error());

$emails = imap_search($inbox,'ALL');


if($emails)
{
    $output = '';

    rsort($emails);

    foreach($emails as $email_number) 
    {
        $header=imap_headerinfo($inbox,$email_number);

        $from = $header->from[0]->mailbox . "@" . $header->from[0]->host;
        $toaddress=$header->toaddress;
        $replyto=$header->reply_to[0]->mailbox."@".$header->reply_to[0]->host;
        $datetime=date("Y-m-d H:i:s",$header->udate);
        $subject=$header->subject;

        //remove the " from the $toaddress
        $toaddress = str_replace('"','',$toaddress);

        echo '<strong>To:</strong> '.$toaddress.'<br>';
        echo '<strong>From:</strong> '.$from.'<br>';
        echo '<strong>Subject:</strong> '.$subject.'<br>';

        //get message body
        $message = (imap_fetchbody($inbox,$email_number,1.1)); 
        if($message == '')
        {
            $message = (imap_fetchbody($inbox,$email_number,1));
        }
}

It works fine, however on some emails in the body I get = in between words, or =20 in between words. And other times the emails will just be blank even though they are not blank when sent.

This only happens when coming from certain emails.

How can I get round this and just make the email completely plain text?

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

This happens because the emails are normally Quoted-printable encoded. The = is a soft line break and =20 is a white space. I think, you could use quoted_printable_decode() on the message so it shows correctly. About the blank emails, I don’t know, I would need more details.

Basically:

//get message body
$message = quoted_printable_decode(imap_fetchbody($inbox,$email_number,1.1));

Method 2

$data = imap_fetchbody($this->imapStream, $Part->uid, $Part->path, FT_UID | FT_PEEK);
if ($Part->format === 'quoted-printable' && $data) {
    $data = quoted_printable_decode($data);
}

This is required for mails with

Content-Transfer-Encoding: quoted-printable

But for mails with

Content-Transfer-Encoding: 8bit

simply imap_fetchbody is enough.

Above code was taken from a cake-php component created for fetching mails from mail boxes throgh IMAP.

Method 3

I made an entire class some years ago, and I’m still using it when I need to get contents from emails. It will help you fetch all email bodies (sometimes you have html and plain text) in a readable format, and get all attached files, just ready to be saved somewhere or sent to a website user.

It is not really optimized so on a big mailbox you may have troubles; but the purpose of this class was to access emails in a readable format to put them on a widget of a website. I let you play with the sample below to get how it works.

ImapReader.class.php Here is the source code.

<?php

class ImapReader
{

    private $host;
    private $port;
    private $user;
    private $pass;
    private $box;
    private $box_list;
    private $errors;
    private $connected;
    private $list;
    private $deleted;

    const FROM = 0;
    const TO = 1;
    const REPLY_TO = 2;
    const SUBJECT = 3;
    const CONTENT = 4;
    const ATTACHMENT = 5;

    public function __construct($host = null, $port = '143', $user = null, $pass = null)
    {
        $this->host = $host;
        $this->port = $port;
        $this->user = $user;
        $this->pass = $pass;
        $this->box = null;
        $this->box_list = null;
        $this->errors = array ();
        $this->connected = false;
        $this->list = null;
        $this->deleted = false;
    }

    public function __destruct()
    {
        if ($this->isConnected())
        {
            $this->disconnect();
        }
    }

    public function changeServer($host = null, $port = '143', $user = null, $pass = null)
    {
        if ($this->isConnected())
        {
            $this->disconnect();
        }
        $this->host = $host;
        $this->port = $port;
        $this->user = $user;
        $this->pass = $pass;
        $this->box_list = null;
        $this->errors = array ();
        $this->list = null;
        return $this;
    }

    public function canConnect()
    {
        return (($this->connected == false) && (is_string($this->host)) && (!empty($this->host))
           && (is_numeric($this->port)) && ($this->port >= 1) && ($this->port <= 65535)
           && (is_string($this->user)) && (!empty($this->user)) && (is_string($this->pass)) && (!empty($this->pass)));
    }

    public function connect()
    {
        if ($this->canConnect())
        {
            $this->box = @imap_open("{{$this->host}:{$this->port}/imap/ssl/novalidate-cert}INBOX", $this->user,
                  $this->pass);
            if ($this->box !== false)
            {
                $this->_connected();
            }
            else
            {
                $this->errors = array_merge($this->errors, imap_errors());
            }
        }
        return $this;
    }

    public function boxList()
    {
        if (is_null($this->box_list))
        {
            $list = imap_getsubscribed($this->box, "{{$this->host}:{$this->port}}", "*");
            $this->box_list = array ();
            foreach ($list as $box)
            {
                $this->box_list[] = $box->name;
            }
        }
        return $this->box_list;
    }

    public function fetchAllHeaders($mbox)
    {
        if ($this->isConnected())
        {
            $test = imap_reopen($this->box, "{$mbox}");
            if (!$test)
            {
                return false;
            }
            $num_msgs = imap_num_msg($this->box);
            $this->list = array ();
            for ($id = 1; ($id <= $num_msgs); $id++)
            {
                $this->list[] = $this->_fetchHeader($mbox, $id);
            }
            return true;
        }
        return false;
    }

    public function fetchSearchHeaders($mbox, $criteria)
    {
        if ($this->isConnected())
        {
            $test = imap_reopen($this->box, "{$mbox}");
            if (!$test)
            {
                return false;
            }
            $msgs = imap_search($this->box, $criteria);
            if ($msgs)
            {
                foreach ($msgs as $id)
                {
                    $this->list[] = $this->_fetchHeader($mbox, $id);
                }
            }
            return true;
        }
        return false;
    }

    public function isConnected()
    {
        return $this->connected;
    }

    public function disconnect()
    {
        if ($this->connected)
        {
            if ($this->deleted)
            {
                imap_expunge($this->box);
                $this->deleted = false;
            }
            imap_close($this->box);
            $this->connected = false;
            $this->box = null;
        }
        return $this;
    }

    /**
     * Took from khigashi dot oang at gmail dot com at php.net
     * with replacement of ereg family functions by preg's ones.
     *
     * @param string $str
     * @return string
     */
    private function _fix($str)
    {
        if (preg_match("/=?.{0,}?[Bb]?/", $str))
        {
            $str = preg_split("/=?.{0,}?[Bb]?/", $str);
            while (list($key, $value) = each($str))
            {
                if (preg_match("/?=/", $value))
                {
                    $arrTemp = preg_split("/?=/", $value);
                    $arrTemp[0] = base64_decode($arrTemp[0]);
                    $str[$key] = join("", $arrTemp);
                }
            }
            $str = join("", $str);
        }

        if (preg_match("/=?.{0,}?Q?/", $str))
        {
            $str = quoted_printable_decode($str);
            $str = preg_replace("/=?.{0,}?[Qq]?/", "", $str);
            $str = preg_replace("/?=/", "", $str);
        }
        return trim($str);
    }

    private function _connected()
    {
        $this->connected = true;
        return $this;
    }

    public function getErrors()
    {
        $errors = $this->errors;
        $this->errors = array ();
        return $errors;
    }

    public function count()
    {
        if (is_null($this->list))
        {
            return 0;
        }
        return count($this->list);
    }

    public function get($nbr = null)
    {
        if (is_null($nbr))
        {
            return $this->list;
        }
        if ((is_array($this->list)) && (isset($this->list[$nbr])))
        {
            return $this->list[$nbr];
        }
        return null;
    }

    public function fetch($nbr = null)
    {
        return $this->_callById('_fetch', $nbr);
    }

    private function _fetchHeader($mbox, $id)
    {
        $header = imap_header($this->box, $id);
        if (!is_object($header))
        {
            return;
        }
        $mail = new stdClass();
        $mail->id = $id;
        $mail->mbox = $mbox;
        $mail->timestamp = (isset($header->udate)) ? ($header->udate) : ('');
        $mail->date = date("d/m/Y H:i:s", (isset($header->udate)) ? ($header->udate) : (''));
        $mail->from = $this->_fix(isset($header->fromaddress) ? ($header->fromaddress) : (''));
        $mail->to = $this->_fix(isset($header->toaddress) ? ($header->toaddress) : (''));
        $mail->reply_to = $this->_fix(isset($header->reply_toaddress) ? ($header->reply_toaddress) : (''));
        $mail->subject = $this->_fix(isset($header->subject) ? ($header->subject) : (''));
        $mail->content = array ();
        $mail->attachments = array ();
        $mail->deleted = false;
        return $mail;
    }

    private function _fetch($mail)
    {
        $test = imap_reopen($this->box, "{$mail->mbox}");
        if (!$test)
        {
            return $mail;
        }
        $structure = imap_fetchstructure($this->box, $mail->id);
        if ((!isset($structure->parts)) || (!is_array($structure->parts)))
        {
            $body = imap_body($this->box, $mail->id);
            $content = new stdClass();
            $content->type = 'content';
            $content->mime = $this->_fetchType($structure);
            $content->charset = $this->_fetchParameter($structure->parameters, 'charset');
            $content->data = $this->_decode($body, $structure->type);
            $content->size = strlen($content->data);
            $mail->content[] = $content;
            return $mail;
        }
        else
        {
            $parts = $this->_fetchPartsStructureRoot($mail, $structure);
            foreach ($parts as $part)
            {
                $content = new stdClass();
                $content->type = null;
                $content->data = null;
                $content->mime = $this->_fetchType($part->data);
                if ((isset($part->data->disposition))
                   && ((strcmp('attachment', $part->data->disposition) == 0)
                   || (strcmp('inline', $part->data->disposition) == 0)))
                {
                    $content->type = $part->data->disposition;
                    $content->name = null;
                    if (isset($part->data->dparameters))
                    {
                        $content->name = $this->_fetchParameter($part->data->dparameters, 'filename');
                    }
                    if (is_null($content->name))
                    {
                        if (isset($part->data->parameters))
                        {
                            $content->name = $this->_fetchParameter($part->data->parameters, 'name');
                        }
                    }
                    $mail->attachments[] = $content;
                }
                else if ($part->data->type == 0)
                {
                    $content->type = 'content';
                    $content->charset = null;
                    if (isset($part->data->parameters))
                    {
                        $content->charset = $this->_fetchParameter($part->data->parameters, 'charset');
                    }
                    $mail->content[] = $content;
                }
                $body = imap_fetchbody($this->box, $mail->id, $part->no);
                if (isset($part->data->encoding))
                {
                    $content->data = $this->_decode($body, $part->data->encoding);
                }
                else
                {
                    $content->data = $body;
                }
                $content->size = strlen($content->data);
            }
        }
        return $mail;
    }

    private function _fetchPartsStructureRoot($mail, $structure)
    {
        $parts = array ();
        if ((isset($structure->parts)) && (is_array($structure->parts)) && (count($structure->parts) > 0))
        {
            foreach ($structure->parts as $key => $data)
            {
                $this->_fetchPartsStructure($mail, $data, ($key + 1), $parts);
            }
        }
        return $parts;
    }

    private function _fetchPartsStructure($mail, $structure, $prefix, &$parts)
    {
        if ((isset($structure->parts)) && (is_array($structure->parts)) && (count($structure->parts) > 0))
        {
            foreach ($structure->parts as $key => $data)
            {
                $this->_fetchPartsStructure($mail, $data, $prefix . "." . ($key + 1), $parts);
            }
        }

        $part = new stdClass;
        $part->no = $prefix;
        $part->data = $structure;

        $parts[] = $part;
    }

    private function _fetchParameter($parameters, $key)
    {
        foreach ($parameters as $parameter)
        {
            if (strcmp($key, $parameter->attribute) == 0)
            {
                return $parameter->value;
            }
        }
        return null;
    }

    private function _fetchType($structure)
    {
        $primary_mime_type = array ("TEXT", "MULTIPART", "MESSAGE", "APPLICATION", "AUDIO", "IMAGE", "VIDEO", "OTHER");
        if ((isset($structure->subtype)) && ($structure->subtype) && (isset($structure->type)))
        {
            return $primary_mime_type[(int) $structure->type] . '/' . $structure->subtype;
        }
        return "TEXT/PLAIN";
    }

    private function _decode($message, $coding)
    {
        switch ($coding)
        {
            case 2:
                $message = imap_binary($message);
                break;
            case 3:
                $message = imap_base64($message);
                break;
            case 4:
                $message = imap_qprint($message);
                break;
            case 5:
                break;
            default:
                break;
        }
        return $message;
    }

    private function _callById($method, $data)
    {
        $callback = array ($this, $method);

        // data is null
        if (is_null($data))
        {
            $result = array ();
            foreach ($this->list as $mail)
            {
                $result[] = $this->_callById($method, $mail);
            }
            return $result;
        }

        // data is an array
        if (is_array($data))
        {
            $result = array ();
            foreach ($data as $elem)
            {
                $result[] = $this->_callById($method, $elem);
            }
            return $result;
        }

        // data is an object
        if ((is_object($data)) && ($data instanceof stdClass) && (isset($data->id)))
        {
            return call_user_func($callback, $data);
        }

        // data is numeric
        if (($this->isConnected()) && (is_array($this->list)) && (is_numeric($data)))
        {
            foreach ($this->list as $mail)
            {
                if ($mail->id == $data)
                {
                    return call_user_func($callback, $mail);
                }
            }
        }

        return null;
    }

    public function delete($nbr)
    {
        $this->_callById('_delete', $nbr);
        return;
    }

    private function _delete($mail)
    {
        if ($mail->deleted == false)
        {
            $test = imap_reopen($this->box, "{$mail->mbox}");
            if ($test)
            {
                $this->deleted = true;
                imap_delete($this->box, $mail->id);
                $mail->deleted = true;
            }
        }
    }

    public function searchBy($pattern, $type)
    {
        $result = array ();
        if (is_array($this->list))
        {
            foreach ($this->list as $mail)
            {
                $match = false;
                switch ($type)
                {
                    case self::FROM:
                        $match = $this->_match($mail->from, $pattern);
                        break;
                    case self::TO:
                        $match = $this->_match($mail->to, $pattern);
                        break;
                    case self::REPLY_TO:
                        $match = $this->_match($mail->reply_to, $pattern);
                        break;
                    case self::SUBJECT:
                        $match = $this->_match($mail->subject, $pattern);
                        break;
                    case self::CONTENT:
                        foreach ($mail->content as $content)
                        {
                            $match = $this->_match($content->data, $pattern);
                            if ($match)
                            {
                                break;
                            }
                        }
                        break;
                    case self::ATTACHMENT:
                        foreach ($mail->attachments as $attachment)
                        {
                            $match = $this->_match($attachment->name, $pattern);
                            if ($match)
                            {
                                break;
                            }
                        }
                        break;
                }
                if ($match)
                {
                    $result[] = $mail;
                }
            }
        }
        return $result;
    }

    private function _nmatch($string, $pattern, $a, $b)
    {
        if ((!isset($string[$a])) && (!isset($pattern[$b])))
        {
            return 1;
        }

        if ((isset($pattern[$b])) && ($pattern[$b] == '*'))
        {
            if (isset($string[$a]))
            {
                return ($this->_nmatch($string, $pattern, ($a + 1), $b) + $this->_nmatch($string, $pattern, $a, ($b + 1)));
            }
            else
            {
                return ($this->_nmatch($string, $pattern, $a, ($b + 1)));
            }
        }

        if ((isset($string[$a])) && (isset($pattern[$b])) && ($pattern[$b] == '?'))
        {
            return ($this->_nmatch($string, $pattern, ($a + 1), ($b + 1)));
        }

        if ((isset($string[$a])) && (isset($pattern[$b])) && ($pattern[$b] == '\'))
        {
            if ((isset($pattern[($b + 1)])) && ($string[$a] == $pattern[($b + 1)]))
            {
                return ($this->_nmatch($string, $pattern, ($a + 1), ($b + 2)));
            }
        }

        if ((isset($string[$a])) && (isset($pattern[$b])) && ($string[$a] == $pattern[$b]))
        {
            return ($this->_nmatch($string, $pattern, ($a + 1), ($b + 1)));
        }

        return 0;
    }

    private function _match($string, $pattern)
    {
        return $this->_nmatch($string, $pattern, 0, 0);
    }

}

ImapReader.demo.php Here is the usage sample

<?php

require_once("ImapReader.class.php");

$box = new ImapReader('example.com', '143', '<a href="https://getridbug.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="7b0814161e19141f023b1e031a160b171e55181416">[email protected]</a>', 'xxxxxxxxxxxx');
$box
   ->connect()
   ->fetchAllHeaders()
;

echo $box->count() . " emails in mailboxn";
for ($i = 0; ($i < $box->count()); $i++)
{
    $msg = $box->get($i);
    echo "Reception date : {$msg->date}n";
    echo "From : {$msg->from}n";
    echo "To : {$msg->to}n";
    echo "Reply to : {$msg->from}n";
    echo "Subject : {$msg->subject}n";
    $msg = $box->fetch($msg);
    echo "Number of readable contents : " . count($msg->content) . "n";
    foreach ($msg->content as $key => $content)
    {
        echo "tContent  " . ($key + 1) . " :n";
        echo "ttContent type : {$content->mime}n";
        echo "ttContent charset : {$content->charset}n";
        echo "ttContent size : {$content->size}n";
    }
    echo "Number of attachments : " . count($msg->attachments) . "n";
    foreach ($msg->attachments as $key => $attachment)
    {
        echo "tAttachment " . ($key + 1) . " :n";
        echo "ttAttachment type : {$attachment->type}n";
        echo "ttContent type : {$attachment->mime}n";
        echo "ttFile name : {$attachment->name}n";
        echo "ttFile size : {$attachment->size}n";
    }
    echo "n";
}

echo "Searching '*Bob*' ...n";
$results = $box->searchBy('*Bob*', ImapReader::FROM);
foreach ($results as $result)
{
    echo "tMatched: {$result->from} - {$result->date} - {$result->subject}n";
}

Enjoy

Method 4

Regarding the blank emails, check the encoding of the mail.

If it is a binary encoded mail then you will get blank mails when you try to insert them into a mysql text field.

Try shifting every mail to UTF-8 and then insert it

iconv(mb_detect_encoding($mail_content, mb_detect_order(), true), “UTF-8”, $mail_content);

Method 5

function getmsg($mbox,$mid) {
    // input $mbox = IMAP stream, $mid = message id
    // output all the following:
    global $charset,$htmlmsg,$plainmsg,$attachments;
    $htmlmsg = $plainmsg = $charset = '';
    $attachments = array();

    // HEADER
    $h = imap_header($mbox,$mid);
    // add code here to get date, from, to, cc, subject...

    // BODY
    $s = imap_fetchstructure($mbox,$mid);
    if (!$s->parts)  // simple
        getpart($mbox,$mid,$s,0);  // pass 0 as part-number
    else {  // multipart: cycle through each part
        foreach ($s->parts as $partno0=>$p)
            getpart($mbox,$mid,$p,$partno0+1);
    }
}

function getpart($mbox,$mid,$p,$partno) {
    // $partno = '1', '2', '2.1', '2.1.3', etc for multipart, 0 if simple
    global $htmlmsg,$plainmsg,$charset,$attachments;

    // DECODE DATA
    $data = ($partno)?
        imap_fetchbody($mbox,$mid,$partno):  // multipart
        imap_body($mbox,$mid);  // simple
    // Any part may be encoded, even plain text messages, so check everything.
    if ($p->encoding==4)
        $data = quoted_printable_decode($data);
    elseif ($p->encoding==3)
        $data = base64_decode($data);

    // PARAMETERS
    // get all parameters, like charset, filenames of attachments, etc.
    $params = array();
    if ($p->parameters)
        foreach ($p->parameters as $x)
            $params[strtolower($x->attribute)] = $x->value;
    if ($p->dparameters)
        foreach ($p->dparameters as $x)
            $params[strtolower($x->attribute)] = $x->value;

    // ATTACHMENT
    // Any part with a filename is an attachment,
    // so an attached text file (type 0) is not mistaken as the message.
    if ($params['filename'] || $params['name']) {
        // filename may be given as 'Filename' or 'Name' or both
        $filename = ($params['filename'])? $params['filename'] : $params['name'];
        // filename may be encoded, so see imap_mime_header_decode()
        $attachments[$filename] = $data;  // this is a problem if two files have same name
    }

    // TEXT
    if ($p->type==0 && $data) {
        // Messages may be split in different parts because of inline attachments,
        // so append parts together with blank row.
        if (strtolower($p->subtype)=='plain')
            $plainmsg. = trim($data) ."nn";
        else
            $htmlmsg. = $data ."<br><br>";
        $charset = $params['charset'];  // assume all parts are same charset
    }

    // EMBEDDED MESSAGE
    // Many bounce notifications embed the original message as type 2,
    // but AOL uses type 1 (multipart), which is not handled here.
    // There are no PHP functions to parse embedded messages,
    // so this just appends the raw source to the main message.
    elseif ($p->type==2 && $data) {
        $plainmsg. = $data."nn";
    }

    // SUBPART RECURSION
    if ($p->parts) {
        foreach ($p->parts as $partno0=>$p2)
            getpart($mbox,$mid,$p2,$partno.'.'.($partno0+1));  // 1.2, 1.2.1, etc.
    }
}

Reference (First user contributed note): http://php.net/manual/en/function.imap-fetchstructure.php

Method 6

I tried all this answers, but neither one worked for me. Then I hit first user contributed note on this PHP page:

http://php.net/manual/en/function.imap-fetchstructure.php

and this works for all my cases. Quite old answer btw.


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x