Exploring the PHP IMAP Library, Part 2

This entry is part 2 of 2 in the series Exploring the PHP IMAP Library

Exploring the PHP IMAP Library

In the first part of this series I discussed how to connect to IMAP servers using the PHP IMAP extension. In this part we’ll complete the series by discussing working with folders and reading email content. Let’s get started!

Working with Email Flags

A list of flags are usually associated with each message: unread, replied, flagged, draft, etc. We can check the message’s Unseen property to identify whether it has been read or not. If the message has been viewed then we’ll get the value “U”. So going back to the code from part one, let’s modify it to show the read/unread status.

<?php
$numMessages = imap_num_msg($imap);
for ($i = $numMessages; $i > ($numMessages - 20); $i--) {
    $header = imap_header($imap, $i);

    ...

    $uid = imap_uid($imap, $i);
    $class = ($header->Unseen == "U") ? "unreadMsg" : "readMsg";

    echo "<ul class="' . $class . '">';
    echo "<li><strong>From:</strong>" . $details["fromName"];
    echo " " . $details["fromAddr"] . "</li>";
    echo "<li><strong>Subject:</strong> " . $details["subject"] . "</li>";
    echo '<li><a href="mail.php?folder=' . $folder . '&uid=' . $uid . '&func=read">Read</a>';
    echo " | ";
    echo '<a href="mail.php?folder=' . $folder . '&uid=' . $uid . '&func=delete">Delete</a></li>';
    echo "</ul>";
}

We can check the status of the Unseen property assign different CSS classes to the markup which can show the read/unread email information differently.

.unreadMsg {
    color: #000;
    font-weight: bold;
}

.readMsg {
    color: #999;
}

We can also create special flags on emails. For example, let’s say we wanted to mark a message as starred. For this we use the Flagged property which will have the value “F” if the message is flagged.

<?php
if ($header->Flagged == "F") {
    $class .= " flaggedMsg";
}

To set flags on a message, we use the impa_setflag_full() function.

<?php
$status = imap_setflag_full($imap, $uid, "\Seen \Flagged", ST_UID);

The above code marks the message as “Read(\Seen)” and sets the flagged status to “F”. I always prefer using a UID instead of email sequence number as the second parameter, so I must set the optional fourth parameter with the constant ST_UID. You can provide other flags, such as Draft, Deleted, and Answered with this function too.

Although I have set the flags on just a single message, you can provide a range like “1,10” as the second parameter for setting flags on multiple messages if you’d like.

Deleting Email Messages

The imap_delete() function is used to delete messages. It marks them for deletion, but doesn’t actually remove them from your account. The imap_expunge() function is responsible for actually deleting the marked messages.

<?php
imap_delete($imap, $uid, FT_UID);
imap_expunge($imap);

I’ve called the delete function with a UID instead of sequence number. Otherwise, I risk losing important messages because of changes in sequence numbers (remember from part one that sequence numbers are not unique).

Viewing Email Attachments

Next to reading and sending emails, working with email attachments is probably the next most important feature of an email client. We’ll focus on checking for email attachments, displaying them, and downloading them.

There are various methods for reading the structure of a message and identifying attachments. The library mentioned in the first part, the Receive Mail class developed by Mitul Koradia, has features for downloading them as well. But here I’ll use functions included in the comments section for the imap_fetchstructure() function which I think is the easiest way.

Before taking a look at some code, I’d like to show you the structure of an email with attachments as returned by imap_fetchstructure().

stdClass Object
(
  [type] => 1
  [encoding] => 0
  [ifsubtype] => 1
  [subtype] => MIXED
  [ifdescription] => 0
  [ifid] => 0
  [ifdisposition] => 0
  [ifdparameters] => 0
  [ifparameters] => 1
  [parameters] => Array
    (
      [0] => stdClass Object
        (
            [attribute] => BOUNDARY
            [value] => bcaec54b516462cef304c7e9d5c3
        )
    )
  [parts] => Array
    (
      [0] => stdClass Object
        (
          [type] => 1
          [encoding] => 0
          [ifsubtype] => 1
          [subtype] => ALTERNATIVE
          [ifdescription] => 0
          [ifid] => 0
          [ifdisposition] => 0
          [ifdparameters] => 0
          [ifparameters] => 1
          [parameters] => Array
            (
              [0] => stdClass Object
                (
                  [attribute] => BOUNDARY
                  [value] => bcaec54b516462ceeb04c7e9d5c1
                )
            )
          [parts] => Array
            (
              [0] => stdClass Object
                (
                  [type] => 0
                  [encoding] => 0
                  [ifsubtype] => 1
                  [subtype] => PLAIN
                  [ifdescription] => 0
                  [ifid] => 0
                  [lines] => 1
                  [bytes] => 2
                  [ifdisposition] => 0
                  [ifdparameters] => 0
                  [ifparameters] => 1
                  [parameters] => Array
                    (
                      [0] => stdClass Object
                        (
                          [attribute] => CHARSET
                          [value] => ISO-8859-1
                        )
                    )
                )
              [1] => stdClass Object
                (
                  [type] => 0
                  [encoding] => 0
                  [ifsubtype] => 1
                  [subtype] => HTML
                  [ifdescription] => 0
                  [ifid] => 0
                  [lines] => 1
                  [bytes] => 6
                  [ifdisposition] => 0
                  [ifdparameters] => 0
                  [ifparameters] => 1
                  [parameters] => Array
                    (
                      [0] => stdClass Object
                        (
                          [attribute] => CHARSET
                          [value] => ISO-8859-1
                        )
                    )
                )
            )
        )
      [1] => stdClass Object
        (
          [type] => 3
          [encoding] => 3
          [ifsubtype] => 1
          [subtype] => ZIP
          [ifdescription] => 0
          [ifid] => 0
          [bytes] => 115464
          [ifdisposition] => 1
          [disposition] => ATTACHMENT
          [ifdparameters] => 1
          [dparameters] => Array
            (
              [0] => stdClass Object
                (
                  [attribute] => FILENAME
                  [value] => weekly-reports.zip
                )
            )
          [ifparameters] => 1
          [parameters] => Array
            (
              [0] => stdClass Object
                (
                  [attribute] => NAME
                  [value] => weekly-reports.zip
                )
            )
        )
    )
)

If you look carefully at the structure, you’ll see the attachment as the part with disposition equal to “ATTACHMENT”. This mail has 1 attachment, but it is entirely possible to have multiple attachments and thus multiple parts with “ATTACHMENT”. We can easily identify attachments by checking this parameter.

<?php
$mailStruct = imap_fetchstructure($imap, $i);
$attachments = getAttachments($imap, $i, $mailStruct, "");

Inside the viewMailbox() function I have included the above lines. First we get the structure of each mail using imap_fetchstructure() function. It will return an object like the one shown previously. Then we call the getAttachments() function, which will provide the attachment details.

<?php
function getAttachments($imap, $mailNum, $part, $partNum) {
    $attachments = array();

    if (isset($part->parts)) {
        foreach ($part->parts as $key => $subpart) {
            if($partNum != "") {
                $newPartNum = $partNum . "." . ($key + 1);
            }
            else {
                $newPartNum = ($key+1);
            }
            $result = getAttachments($imap, $mailNum, $subpart,
                $newPartNum);
            if (count($result) != 0) {
                 array_push($attachments, $result);
             }
        }
    }
    else if (isset($part->disposition)) {
        if ($part->disposition == "ATTACHMENT") {
            $partStruct = imap_bodystruct($imap, $mailNum,
                $partNum);
            $attachmentDetails = array(
                "name"    => $part->dparameters[0]->value,
                "partNum" => $partNum,
                "enc"     => $partStruct->encoding
            );
            return $attachmentDetails;
        }
    }

    return $attachments;
}

First we check whether parts are set for the current email and then we have to traverse through each part recursively. We have to modify the part number and pass it to the recursive call. As you can see, subpart numbers are broken up into dotted segments. If you have 3 levels of a part number, it would be something like 1.0.1.

When further parts are not available, we check whether the disposition parameter is available and whether its value is “ATTACHMENT”. In such situations we get the structure of the given part using imap_bodystruct(). Both imap_bodystruct() and imap_fetchstructure() provides the same output. The only difference between the two is that we can use imap_bodystruct() to get specific part information instead of the entire structure.

Now we have the list of attachment details for the given email and we loop through all the attachments and display download links for them:

<?php
echo "Attachments: ";
foreach ($attachments as $attachment) {

echo '<a href="mail.php?func=' . $func . '&folder=' . $folder . '&uid=' . $uid .
    '&part=' . $attachment["partNum"] . '&enc=' . $attachment["enc"] . '">' .
    $attachment["name"] . "</a>";
}

Downloading Attachments

To download an attachment, we need the email’s UID, the part number, and the encoding type of the attachment. I’ve included those parameters in the download link created above. Once the link is clicked, the following function can be invoked:

<?php
function downloadAttachment($imap, $uid, $partNum, $encoding, $path) {
    $partStruct = imap_bodystruct($imap, imap_msgno($imap, $uid), $partNum);

    $filename = $partStruct->dparameters[0]->value;
    $message = imap_fetchbody($imap, $uid, $partNum, FT_UID);

    switch ($encoding) {
        case 0:
        case 1:
            $message = imap_8bit($message);
            break;
        case 2:
            $message = imap_binary($message);
            break;
        case 3:
            $message = imap_base64($message);
            break;
        case 4:
            $message = quoted_printable_decode($message);
            break;
    }

    header("Content-Description: File Transfer");
    header("Content-Type: application/octet-stream");
    header("Content-Disposition: attachment; filename=" . $filename);
    header("Content-Transfer-Encoding: binary");
    header("Expires: 0");
    header("Cache-Control: must-revalidate");
    header("Pragma: public");
    echo $message;
}

First we need to get the structure of the given part to identify the attachment name, which is done with imap_bodystruct(). You can see that I used imap_msgno() to get the sequence number from the UID; this is because imap_bodystruct() doesn’t accept a UID so we have to convert the UID into the sequence number.

Next we get the attachment content using imap_fetchbody(). It will only receive the contents of the given part number. Then we use the appropriate content decoding function according to the encoding type of the given attachment to decode it.

Finally, we output the attachment content with appropriate headers so the browser will download the file.

Summary

We’ve completed our look at PHP’s IMAP functions and you should now have an understanding sufficient enough to put together a simple working email reader. Be sure to learn about the other functions that are available and explore them as well to broaden your understanding.

Image via Fotolia

Exploring the PHP IMAP Library

<< Exploring the PHP IMAP Library, Part 1

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Ruslan

    Hi Rakhitha!
    Thank you for this article!
    Maybe my question is off topic, but I would like to ask you for advice on how to make a long connection to IMAP.
    Is it possible to make so that the connection to IMAP was kept for a long time? In order to receive new messages at once, when they will be in my box? Like the TheBat!.

    • http://www.innovativephp.com Rakhitha Nimesh

      Hi Ruslan
      Actually its a good question. Opening an imap connection in every request can be a considerable overhead for large applications. I have been using new imap connection on every request since I was workinng on a simple mail client.

      But I have found something called imap proxy which keeps the connection. ”
      SquirrelMail’s IMAP Proxy is a caching IMAP proxy server intended for use with webmail clients that cannot maintain persistent connections to an IMAP server.” You can check that using http://imapproxy.org/

  • http://vamsii.com/blog Vamsi
  • Jop

    Nice article, thanks!

    I hava a question about the extraction of the attachments.
    When initiating the recursive localization of attachments, you call
    <?php
    $mailStruct = imap_fetchstructure($imap, $i);
    $attachments = getAttachments($imap, $i, $mailStruct, "");

    In the recursive function getAttachments() you call imap_bodystruct(). Why?
    In a section below, you state the output for imap_bodystruct() and imap_fetchstructure() are equal, except imap_bodystruct() only returns information for a specific part. You've already called imap_fetchstructure(), so I'd guess all information is already present and there is no need to call imap_bodystruct() again. Is the information about the encoding not present in the imap_fetchstructure()?

    Second (related) question: does the invocation of imap_bodystruct() trigger an extra request over the internet?

    • http://www.innovativephp.com Rakhitha Nimesh

      Hi Jop

      Thanks for your suggestions and it seems a good point.

      I think imap_bodystruct will trigger another request as its using the connection object. Therefore its always better to limit the number of requests in such application.

      Initially we pass the whole structure of mail to the getAttachments function using imap_fetchstructure. But we have to go through each sub part recursively. When the recursive calling happens we pass the relevent part of struture as parameter and whole struture doesnt exists anymore.

      So we have to use imap_bodystruct again to get the encoding type of attachment.

      If you have a solution without calling the function multiple times let me know.I am open to suggestions and improving the code :)

  • http://softackle.com Muhammad Naveed

    Hello,
    Thanks for nice tutorial. I tried but i got some issues on my side showing this error “Fatal error: Call to undefined function viewMail()” and if i remove all switch functions then showing all mails but not working read and delete action.

  • Roy Walter

    You don’t need if ( isset($part->disposition) ) because the struct already has $part->ifdisposition.
    R.