HomePHP Page 4 - Building A PHP-Based Mail Client (part 2)
Structure And Syntax - PHP
Now that you've got a basic mail reader up and running, it's timeto learn a little more about attachments. This second segment analyzesMIME-encoded attachments, demonstrating how to decode and download them,and then integrates attachment handling features into the primitive mailclient previously demonstrated.
Using this process flow as reference, let's now begin implementing the code for the mail client.
A quick look at the PHP manual reveals that PHP provides a number of useful functions to assist in this process:
1. imap_fetchstructure() - read and return information on message structure
2. imap_header() - return an object containing header elements
3. imap_body() - retrieve the complete message body
4. imap_fetchbody() - retrieve a specific section of the body
Of these, you've already seen the imap_fetchstructure() function in action - using a mailbox handle and message number as arguments, this function reads the message body and returns an object containing information on the message size, message body and MIME parts within it.
This object exposes a "parts" property, which is itself an array; the elements of this array are objects representing the various sections of the message. Therefore, it's possible to identify whether or not a message contains multiple parts, and obtain information on those parts, simply by iterating through this array.
Each of the objects in the "parts" array provides information on the corresponding message section - the encoding, the size, the type and subtype, the filename and so on. A complete list of the properties exposed by each of these objects is available in the PHP manual at http://www.php.net/manual/en/function.imap-fetchstructure.php - you should look at it before proceeding further.
Using this information, it's possible to write a couple of simple functions that build on imap_fetchstructure() to deliver customized information about message sections:
<?
// define some constants
// message types
$type = array("text", "multipart", "message", "application", "audio",
"image", "video", "other");
// message encodings
$encoding = array("7bit", "8bit", "binary", "base64", "quoted-printable",
"other");
// parse message body
function parse($structure)
{
global $type;
global $encoding;
// create an array to hold message sections
$ret = array();
// split structure into parts
$parts = $structure->parts;
for($x=0; $x<sizeof($parts); $x++)
{
$ret[$x]["pid"] = ($x+1);
$this = $parts[$x];
// default to text
if ($this->type == "") { $this->type = 0; }
$ret[$x]["type"] = $type[$this->type] . "/" . strtolower($this->subtype);
// default to 7bit
if ($this->encoding == "") { $this->encoding = 0; }
$ret[$x]["encoding"] = $encoding[$this->encoding];
$ret[$x]["size"] = strtolower($this->bytes);
$ret[$x]["disposition"] = strtolower($this->disposition);
if (strtolower($this->disposition) == "attachment")
{
$params = $this->dparameters;
foreach ($params as $p)
{
if($p->attribute == "FILENAME")
{
$ret[$x]["name"] = $p->value;
break;
}
}
}
}
return $ret;
}
?>
The parse() function accepts an object, as returned by
imap_fetchstructure(), and parses it to produce an array holding some very specific information on each message part.
First, parse() creates an empty array named $ret, which will ultimately contain as many elements as there are message parts.
<?
// create an array to hold message sections
$ret = array();
?>
Every element of $ret will itself be an associative array,
with keys representing the part number, part type, encoding, disposition, size and filename.
Next, parse() creates an array named $parts, to hold the object array returned by the "parts" property of the imap_fetchstructure() object.
<?
// split structure into parts
$parts = $structure->parts;
?>
parse() then iterates through $parts, adding each element to
$ret and creating keys to represent the characteristics of each part found.
The IMAP specification (available at
http://www.faqs.org/rfcs/rfc2060.html) defines a number, or part ID, for every part of a multipart message, starting from 1 (which is usually the message text itself); my array defines this part ID via the "pid" key, and I'm recording it at this stage itself because I'm sure to need it when handling messages containing more than one attachment.
The type and encoding of each part are stored as integers by imap_fetchstructure(); these correspond to elements of the $type and $encoding arrays respectively. If you look at the complete function definition above, you'll see that these arrays are defined outside the function, and imported into it with the "global" keyword.
Here's an example of the array returned by parse():
As you can see, this is a fairly clear representation of the
various sections that make up a MIME message.{mospagebreak title=Getting Attached} I've also written another function to extract the attachments only from the array returned by parse() - take a look at the get_attachments() function:
<?
// iterate through object returned by parse()
// create a new array holding information only on message attachments
function get_attachments($arr)
{
for($x=0; $x<sizeof($arr); $x++)
{
if($arr[$x]["disposition"] == "attachment")
{
$ret[] = $arr[$x];
}
}
return $ret;
}
?>
Let's now backtrack a little and add this code to the message
listing, "list.php", where it will be used to identify which messages have attachments (so that an attachment icon can be displayed next to those messages).