Building A PHP-Based Mail Client (part 2) - Structure And Syntax
(Page 4 of 7 )
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.
<?
for($x=0; $x<sizeof($parts); $x++)
{
$ret[$x]["pid"] = ($x+1);
$ret[$x]["type"] = $type[$this->type] . "/" . strtolower($this->subtype);
$ret[$x]["encoding"] = $encoding[$this->encoding];
$ret[$x]["size"] = strtolower($this->bytes);
$ret[$x]["disposition"] = strtolower($this->disposition);
// some parts snipped for brevity
}
?>
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():
Array
(
[0] => Array
(
[pid] => 1
[type] => text/plain
[encoding] => 7bit
[size] => 41
[disposition] =>
)
[1] => Array
(
[pid] => 2
[type] => application/zip
[encoding] => base64
[size] => 50634
[disposition] => attachment
[name] => photos1.zip
)
[2] => Array
(
[pid] => 3
[type] => application/zip
[encoding] => base64
[size] => 44882
[disposition] => attachment
[name] => photos2.zip
)
)
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).
Here's the revised version of that script:
<?php
// list.php - display message list
// includes
include("functions.php");
// session check
session_start();
if (!session_is_registered("SESSION"))
{
header("Location: error.php?ec=2");
exit;
}
// open mailbox
$inbox = @imap_open ("{". $SESSION_MAIL_HOST . "/pop3:110}",
$SESSION_USER_NAME, $SESSION_USER_PASS) or header("Location:
error.php?ec=3");
// get number of messages
$total = imap_num_msg($inbox);
?>
<html>
<head>
</head>
<body bgcolor="White">
<?
// page header
?>
<table width="100%" border="0" cellspacing="3" cellpadding="5">
<!-- command buttons - snipped -->
</table>
<?
if ($total > 0)
{
?>
<table width="100%" border="0" cellspacing="0" cellpadding="5">
<form action="delete.php" method="post">
<!-- message info columns -->
<tr>
<td width="5%"><font size="-1"> </font></td>
<td width="5%"><font size="-1"> </font></td>
<td width="15%"><font face="Verdana" size="-1"><b>Date</b></font></td>
<td width="20%"><font face="Verdana" size="-1"><b>From</b></font></td>
<td width="45%"><font face="Verdana" size="-1"><b>Subject</b></font></td>
<td width="10%"><font face="Verdana" size="-1"><b>Size</b></font></td>
</tr>
<?php
// iterate through messages
for($x=$total; $x>0; $x--)
{
// get header and structure
$headers = imap_header($inbox, $x);
$structure = imap_fetchstructure($inbox, $x);
?>
<tr bgcolor="<?php echo $bgcolor; ?>">
<td align="right" valign="top">
<input type="Checkbox" name="dmsg[]" value="<? echo $x; ?>">
</td>
<td valign="top">
<?
// attachment handling code goes here
// parse structure to see if attachments exist
// display icon if so
$sections = parse($structure);
$attachments = get_attachments($sections);
if(is_array($attachments))
{
echo "<img alt=\"Message has attachment\" src=images/list.gif width=30
height=30 border=0>";
}
else
{
echo " ";
}
?>
</td>
<td valign="top">
<font face="Verdana" size="-1"><? echo substr($headers->Date, 0, 22);
?></font>
</td>
<td valign="top">
<font face="Verdana" size="-1"><? echo
htmlspecialchars($headers->fromaddress); ?></font>
</td>
<td valign="top">
<font face="Verdana" size="-1">
<a href="view.php?id=<? echo $x; ?>">
<?
// correction for empty subject
if ($headers->Subject == "")
{
echo "No subject";
}
else
{
echo $headers->Subject;
}
?>
</a>
</font>
</td>
<td valign="top">
<font face="Verdana" size="-1">
<?
// display message size
echo ceil(($structure->bytes/1024)), " KB";
?>
</font>
</td>
</tr>
<?
}
// clean up
imap_close($inbox);
?>
</form>
</table>
<?
}
else
{
echo "<font face=Verdana size=-1>You have no mail at this time</font>";
}
?>
</body>
</html>
And here's what the listing looks like, after making the
addition:

Next: Room With A View >>
More PHP Articles
More By icarus, (c) Melonfire