PHP File Attachments

Related Articles

When I first began working with PHP, one of the features that made PHP so appealing to me was the ability to send an automated e-mail directly from my PHP scripts. As my scripts started to get more advanced, I wanted to do more with my e-mail. I quickly realized that as nice as it was to send automated e-mails, I was limited to text-only e-mails by the built-in mail() function. In order to send attachments, I was going to have to learn the MIME (or Multipurpose Internet Mail Extension) protocol.

Although obviously it is completely possible to send an e-mail that is not MIME encoded (after all, it is an extension), almost all present-day e-mail clients send their e-mails using the MIME encoding method. With MIME, not only could I send attachments with my e-mails, but with a little extra work I could even send enriched e-mails (with bold, italics, etc) or even HTML-rendered e-mail. Here's an example simple MIME e-mail:

From: "John Coggeshall" <john@php.net>

To: "Some User" <user@somewhere.internetaddress.com>

Subject: A MIME e-mail

Date: Fri, 25 Aug 2001 12:13:57 -0400

Message-ID: <00020a28b392c92939d9392$28b2839f28302@computer>

MIME-Version: 1.0

Content-Type: text/plain;

charset="iso-8859-1"

Content-Transfer-Encoding: 7bit

This is the content of a basic MIME base e-mail!

Unlike standard e-mail messages, one of the major differences between this e-mail and a standard plain-text e-mail is the inclusion of the Content-Type header which gives us the ability to define what type of content we are sending. Immediately, I have already improved upon the standard PHP mail() function -- by changing the content type to, say an image format, it should be possible for me to send an image in place of text! However, so far I have to choose -- I either have to send text, or something else, not both. This is where real MIME comes into play -- multipart e-mails.

The process of sending multipart MIME e-mails is fairly straightforward. When the e-mail is first composed, the first Content-Type should be set to the following:

Content-Type: multipart/mixed; boundary="UNIQUE-STRING";

What does this do? This defines this MIME e-mail as one that contains multiple different content-types, each separate piece separated by the string contained within the boundary parameter above. Specifically, the start of each segment is defined by:

--UNIQUE-STRING

With the final segment of the MIME e-mail is defined by:

--UNIQUE-STRING--

Since it is this UNIQUE-STRING which defines the boundaries, it is imperative that it is generated using the MD5 algorithm or similar facility that will produce something very unlikely to exist as an actual part of the e-mail address.

Each segment defined by the MIME e-mail is allowed to have its own headers, including Content-Type, Content-Transfer-Encoding, and Content-Disposition headers. With a little coding, I could now construct MIME encoded e-mails that allowed me to send multiple content-types and a step closer to sending those attachments.

With all of this knowledge in hand, it's time to start actually writing some PHP code to get this new mail function running. Because of the limited scope of what is trying to be accomplished (just sending an e-mail with an attachment), we can condense the complex

Subject of MIME format e-mails into a single PHP function mail_attached(). Let's take a look at the initialization of the function:

<?php

function mail_attached($to, $from, $subject,

$message, $filename, $headers = '') {

$unique_sep = md5(uniqid(time()));

$headers .= "From: $fromn";

$headers .= "MIME-Version: 1.0nContent-Type:"

." multipart/mixed;boundary="$unique_sep";n";

$headers .= "charset="iso-8859-1"nContent-Transfer-Encoding:"

."7bitnn";

$headers .= "If you are reading this, then your e-mail client does"

."not support MIME.n";

$headers .= "Sent with mail_attached() (c) 2002 John

Coggeshallnn";

$headers .= "--$unique_sepn";

As you can see, we start with a very straightforward declaration. The $unique_sep variable represents what was identified as "UNIQUE-STRING" above and will be used to separate the different segments of the MIME e-mail. Next, we initialize the (fairly) static header elements of the e-mail. Note that the message that appears in the fourth and fifth $header assignments are not required, however generally they are considered good practice.

Another point of interest that needs to be discussed before we move forward is the $filename parameter of the mail_attached() function. This parameter is designed to be an array of the following structure:

THIS IS NOT PART OF THE mail_attached() FUNCTION!

$file_array = array(0=>array('file'=>'/complete/path/to/myfile.jpg',

'mimetype'=>'image/jpeg',

'filename'=>'myfile.jpg'));

Where the actual parameter passed is an array of arrays, one for each file to be included. Also note that the file and filename keys are not necessarily the same value -- 'file' represents the completely qualified path to the file to be sent, while 'filename' represents the name to be given to the file when displayed within the receiver’s e-mail client.

Directly following the standard $header elements (immediately after the first segment identifier in the e-mail) is where the headers relating to the first segment of the e-mail begin. By convention, MIME segments should be ordered in such a way so that the most human-readable segments are displayed first. This is to allow those e-mail clients without MIME support to decipher at least portions of the e-mail without having to scroll all over the document. Hence, the first segment in our MIME e-mail will be the plain text message accompanying our attachment as shown:

$headers .= "Content-Type: text/plain; charset="iso-8859-1"n";

$headers .= "Content-Transfer-Encoding: 7bitnn";

$headers .= $message."nn";

At this point, you could simply end the MIME e-mail by appending:

$headers .= "--$unique_sep--";

…and create a properly formatted MIME e-mail that contained nothing but a text message. However, since we're interested in attaching our files to the e-mail, there still is more to do. Here's the next portion of the code:

if(is_array($filename)) {

foreach($filename as $val) {

if(file_exists($val['file'])) {

$headers .= "--$unique_sepn";

$headers .= "Content-Type: {$val['mimetype']}; ".

"name="{$val['filename']}"n";

$headers .= "Content-Transfer-Encoding: ".

"base64n";

$headers .= "Content-Disposition: attachmentnn";

$filedata = implode(file($val['file']), '');

$headers .= chunk_split(base64_encode($filedata));

} else {

echo "Error: File doesn't exist.<BR>";

}

}

} else {

echo "Error: Invalid parameter passed.<BR>";

}

$headers .= "--$unique_sep--n";

Although in this case some simple error checking has been included, the above code functions as follows. For each index of the $filename array passed to the mail_attached() function, a MIME segment is constructed from the associated data. Note that it is here that the file(s) being attached are actually read from the file system and converted into an acceptable format. Because of platform differences, files cannot be directly copied into the MIME e-mail and first must be base-64 encoded and divided using the base64_encode() and chunk_split() PHP functions. Also note that, unlike what we have seen thus far, the Content-Transfer-Encoding header for this segment is not 7bit. This is to inform the client that the data within this section must be decoded prior to processing. Once the MIME segment building process is complete for the e-mail everything is wrapped up and the terminating segment identifier is appended to the end of the $headers variable.

That's basically it. The only thing left to do is send the e-mail using the PHP mail() function (specifying our $headers variable for the forth parameter). Here is what I've used:

if(!mail($to, $subject, $message, $headers)) {

echo "Error: mail() function failed!<BR>";

}

} /* End of mail_attached() function */


Publication Date: Wednesday 23rd July, 2003
Author: John Coggeshall View profile

Related Articles