Exploring the PHP IMAP Library, Part 2
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