Team LiB
Previous Section Next Section

Implementing MIME Email in PHP

At this point, you should have a firm understanding of MIME email and how MIME can be used to implement file attachments, multiple-format email messages, all-in-one HTML email, and much more. In this section of the chapter, I'll show you how to implement this rather complex protocol in your PHP. Although a number of methods are available to you as a PHP developer, including a rather nice method available in the PHP PEAR library (http://pear.php.net/), I will guide you through a series of objects I have written myself for this book. The reason I will not be discussing one of the already existing scripts is because often they are not designed to be educational resources and hence can be difficult both to explain and understand.

If, for whatever reason, you want to use a different script to implement your MIME email after reading this chapter, of course you are welcome to do so. However, if you would like to use the MIME script developed for this book, you can find a complete copy online on this book's official website.

Before I continue, let me warn you that my MIME implementation (as with every MIME script I have ever seen) has been done by using objects in PHP. If you are not comfortable with objects or object-oriented programming in PHP, see Chapter 14, "Object-Oriented Programming in PHP," before continuing with this one.

As I just mentioned, in this section I will discuss a series of objects I have created to implement MIME-based email. These objects have been designed to be intuitive to the MIME protocol itself and require a prior knowledge of the protocol to be used properly. Specifically, a total of five objects are listed in Table 16.2:

Table 16.2. Objects Used to Implement MIME Email

MIMEContainer

The base class, which contains all other segments in a MIME email

MIMESubcontainer

A subcontainer class

MIMEMessage

A container class used to construct message segments of the MIME email

MIMEAttachment

A container class used to attach a file in the MIME email

MIMEContent

A container class used to include content that is related to another segment (such as HTML mail)


How do these classes work? Essentially, as I have alluded to in their descriptions, these classes define "containers," which you can mix and match to construct a complete MIME email. Each one of the preceding classes takes care of all the required headers and so on needed for that particular segment of the email, and the container classes can be "added" to MIMEContainer or MIMESubcontainer. Although I will be describing the internals of these objects in detail, to illustrate their use in Listing 16.4 I have used these classes to send an email with an attachment:

Listing 16.4. Sending an Email Attachment Using MIME
<?php
    require_once("MIMEContainer.class.php");
    require_once("MIMESubcontainer.class.php");
    require_once("MIMEAttachment.class.php");
    require_once("MIMEContent.class.php");
        require_once("MIMEMessage.class.php");

    $email = new MIMEContainer();
    $email->set_content_type("multipart/mixed");
    $message = new MIMEMessage();
    $message->set_content("Hey, here's that file you wanted.\n\n--John");

    $attachment = new MIMEattachment("MIMEContainer.class.php");

    $email->add_subcontainer($message);
    $email->add_subcontainer($attachment);
    $email->sendmail("john@php.net", "angiesue@example.com", "Here's the file");
    echo $email->get_message();

?>

As you can see, I start off the script by creating a new container called $email. This is the main container of the entire MIME emailany other containers must somehow be added into this container to be included. This container has its own boundaries in the MIME email (which are automatically created for us) and will store two segments: the body of the email (stored in the $message container) and the actual attachment (stored in the $attachment container). To add these containers to the subcontainer, we use the add_subcontainer() method. After we have added the containers to the main container, we can then send the mail by the sendmail() method. The result is an email formatted as shown:

To: john@coggeshall.org
Subject: Here's the file
Date: Wed, 23 Dec 2002 12:23:23 -0400
From: angiesue@example.com
MIME-Version: 1.0
Content-Transfer-Encoding: 7-bit
Content-Type: multipart/mixed; boundary=54723de83799b5c76


If you are reading this portion of the e-mail, then you are not reading
this e-mail through a MIME compatible e-mail client.

--54723de83799b5c76
Content-Type: text/plain
Content-Transfer-Encoding: 7-bit

Hey, here's that file you wanted.

--John
--54723de83799b5c76
Content-Type: application/octet-stream; filename=dummy.txt
Content-Transfer-Encoding: base64
Content-Disposition: attachment

InRlc3RpbmcgMSAyIDMiIA0K

--54723de83799b5c76--

NOTE

In the preceding output example, all the headers prior to the MIME-Version header are created by PHP's mail() function, which is used by the sendmail() method.


As you can see, this object-oriented approach makes creation of MIME-based email a pretty simple task! We didn't have to worry about the boundaries, encoding of the files, the appropriate headers, and so on. The work was all done by the MIMEContainer class (which created the basic email and dealt with all the boundaries) and the MIMEAttachment class, which encoded the file, and so on. Curious as to how these objects (and the rest of them) actually work? Let's take a look.

The MIMEContainer and MIMESubcontainer Classes

The most fundamental class I'll be discussing is the MIMEContainer Class. This class defines a number of methods and variables that are used by all the other classes (and that all extend this class). These methods are shown in Table 16.3:

Table 16.3. Methods of the MIMEContainer Base Class

sendmail()

Actually constructs and sends the email using PHP's mail() function.

add_header()

Adds an additional header to a particular segment.

get_add_headers()

Gets an array of all the additional headers for this segment.

set_content_type()

Sets the Content-Type header.

get_content_type()

Returns the Content-Type headers.

set_content_enc()

Sets the Content-Transfer-Encoding header.

set_content()

Sets the content for this object.

get_content()

Returns the content for this object.

add_subcontainer().

Adds a subcontainer (object) into this object (only applies to MIMEContainer or MIMESubcontainer)

get_subcontainers().

Returns an array of subcontainer objects that have been stored in this container (MIMEContainer and MIMESubcontainer only)

create()

Constructs and returns a string representing the appropriate headers for the particular container.


Of all these functions, most are extremely trivial and exist only to maintain good practice when you are developing objects. In fact, the only major function in the MIMEContainer class is the create() function. Hence, let's get all the other functions out of the way and show them for your reference (see Listing 16.5):

Listing 16.5. Trivial Methods of the MIMEContainer Class
<?php

class MIMEContainer {

    protected $content_type = "text/plain";
    protected $content_enc  = "7-bit";
    protected $content;
    protected $subcontainers;
    protected $boundary;
    protected $created;
    protected $add_header;

    public function get_message($add_headers = "") {
        return $this->create($add_headers);
    }

    public function sendmail($to, $from, $subject, $add_headers="") {
        mail($to, $subject, $this->get_message($add_headers),
             "From: $from\r\n");
    }

    function __construct() {
        $this->created = false;
        $this->boundary = uniqid(rand(1,10000));
    }

    public function add_header($header) { $this->add_header[] = $header; }
    public function get_add_headers() { return $this->add_header; }
    public function set_content_type($newval) { $this->content_type = $newval; }
    public function get_content_type() { return $this->content_type; }
    public function get_content_enc() { return $this->content_enc; }
    public function set_content($newval) { $this->content = $newval; }
    public function get_content() { return $this->content; }

    public final function set_content_enc($newval)  {
        $this->content_enc = $newval;
    }

    public final function add_subcontainer($container) {
        $this->subcontainers[] = $container;
    }
    public final function get_subcontainers() { return $this->subcontainers; }
    /* The create() method has been omitted for simplicity, see below
       for a detailed discussion of it. */

}

?>

As you can see, there isn't much to this portion of the object. The real work in this object (as with almost every other object that extends this one) is done in the create() method. This method is responsible for constructing and returning the required MIME headers to construct its appropriate segment of the entire MIME email. When a subcontainer is "added" to the MIMEContainer class, the create() method is called for that subcontainer (and subsequently for any other subcontainers within that subcontainer) as needed during the construction of the email. Hence, every class extending the MIMEContainer class must have a create() method. Because we are currently talking about MIMEContainer, the create() method for it can be found in Listing 16.6:

Listing 16.6. The MIMEContainer create() Function
public function create() {

      /* Standard Headers that exist on every MIME e-mail */
      $headers  = "MIME-Version: 1.0\r\n" .
                  "Content-Transfer-Encoding: {$this->content_enc}\r\n";

      $addheaders = (is_array($this->add_header)) ?
                    implode($this->add_header, "\r\n") : '';

      /* If there is a subcontainer */
      if(is_array($this->subcontainers) &&
         (count($this->subcontainers) > 0)) {

           $headers .= "Content-Type: {$this->content_type}; " .
                       "boundary={$this->boundary}\r\n$addheaders\r\n\r\n";
           $headers = wordwrap("If you are reading this portion of the e-mail," .
                               "then you are not reading this e-mail through a" .
                               " MIME compatible e-mail client\r\n\r\n");

           foreach($this->subcontainers as $val) {
                if(method_exists($val, "create")) {
                   $headers .= "--{$this->boundary}\r\n";
                   $headers .= $val->create();
                }
           }

           $headers .= "--{$this->boundary}--\r\n";
       } else {

           $headers .= "Content-Type: {$this->content_type}\r\n" .
                       $addheaders . "\r\n\r\n{$this->content}";

       }

       return $headers;
    }

As you can see, the create() method for MIMEContainer starts by including the standard headers indicating that this is a MIME email (MIME-Version and Content-Transfer-Encoding). At this time we also convert all (if any) of the additional headers using a combination of a conditional assignment operator and the PHP implode() function.

Whether any subcontainers exist will determine if we specify a boundary parameter for the Content MIMEContent-Type header. This parameter is necessary only if there are subcontainers; therefore, if subcontainers exist, it is assumed that the Content-Type header is set appropriately to a member of the multipart/* family. Assuming that subcontainers do exist, the create() method will create a new boundary marker and attempt to call the create() method for each subcontainer. Because the create() method by definition returns a string representing the headers and content for the particular segment it is constructing, the output from each subcontainer's create() method is added as part of the complete MIME email. This process continues until there are no more subcontainers, at which point the boundary is closed. Because this is the main object, it is safe to assume that the create() function will return a complete MIME email.

The MIMESubcontainer class is virtually identical to the MIMEContainer class in terms of how it works. The major difference between the two classes is how they were designed to be used. Whereas the MIMEContainer class is designed to be the "main" object used in the construction of MIME email, the MIMESubcontainer class is designed to allow for multiboundary MIME emails. This class inherits all its standard functionality from the MIMEContainer class, with the difference that it uses its own boundary value and create() method. Because this has already been discussed earlier, when I talked about the MIMEContainer class, I'll simply provide the code in Listing 16.7:

Listing 16.7. The MIMESubcontainer Class
<?php

    class MIMESubcontainer extends MIMEContainer {

        function __construct() {
            parent::__construct();
        }

        public function create() {
            $addheaders = (is_array($this->add_header)) ?
                              implode($this->add_header, "\r\n") : "";
            $headers =  "Content-Type: {$this->content_type}; boundary=" .
                        "{$this->boundary}\r\n";
            $headers .= "Content-Transfer-Encoding: {$this->content_enc}" .
                        "\r\n$addheaders\r\n";

            if(is_array($this->subcontainers)) {
                foreach($this->subcontainers as $val) {
                    $headers .= "--{$this->boundary}\r\n";
                    $headers .= $val->create();
                }
                $headers .= "--{$this->boundary}--\r\n";
            }
            return $headers;
        }

?>

The MIMEAttachment, MIMEContent, and MIMEMessage Classes

The third class that I'll describe is the MIMEAttachment class. This class is used (as shown in my example found in Listing 16.4) to construct a segment that will render as a file attachment in the email client. Because this class extends the MIMEContainer class, it will automatically inherit all the methods and member variables contained within it. This class does have a unique method available to it, which is used to load and encode the desired file attachmentthe set_file() method found in Listing 16.8 (shown with initial declaration of the class).

Listing 16.8. The MIMEAttachment set_file() Method
<?php

class MIMEAttachment extends MIMEContainer {

    protected $content_type = "application/octet-stream";
    protected $content_enc  = "base64";
    protected $filename;
    protected $content;

    function __construct($filename="", $mimetype="") {
        parent::__construct();

        if(!empty($filename)) {
            $this->set_file($filename, $mimetype);
        }

        $this->content = uniqid(rand(1,1000));
    }

    public function set_file($filename, $mimetype="") {

        $fr = fopen($filename, "r");

        if(!$fr) {
            $classname = __CLASS__;
            trigger_error("[$classname] Couldn't open '$filename' to be attached",
                          E_USER_NOTICE);
            return false;
        }

        if(!empty($mimetype)) {
            $this->content_type = $mimetype;
        }

        $buffer = fread($fr, filesize($filename));
        $this->content = base64_encode($buffer);
        $this->filename = $filename;
        unset($buffer);
        fclose($fr);

        return true;

    }

    public function get_file() {

        $retval = array('filename' => $this->filename,
                        'mimetype' => $this->content_type);

       return $retval;
    }

    /* The create() method is omitted and discussed later */
}

?>

As you can see, the set_file() method accepts two parameters: $filename, representing the file to attach, and an optional parameter, $mimetype, representing the MIME content type for the file. If no content type is provided, the default MIME type of application/octet-stream will be used. The set_file() function then attempts to read in the file and encode it using the base64_encode() PHP function. Assuming everything goes as planned, the set_file() method will then close the file reference and return a Boolean true.

Of course, every class that extends the MIMEContainer class must also have a create() function to construct the necessary headers and the like for the segmentthe MIMEAttachment create() method is no different and can be found in Listing 16.9:

Listing 16.9. The create() Method for MIMEAttachment
public function create() {

        if(!isset($this->content)) {
            return;
        }

        $finfo = pathinfo($this->filename);
        $filename = $finfo['basename'];

        $addheaders = (is_array($this->add_header)) ?
                          implode($this->add_header, "\r\n" :
                          "";

        $headers  = "Content-Type: {$this->content_type}; filename=$filename\r\n";
        $headers .= "Content-Transfer-Encoding: {$this->content_enc}\r\n";
        $headers .= "Content-Disposition: attachment\r\n$addheaders\r\n";
        $headers .= chunk_split($this->content)."\n";

        return $headers;

    }


Because the MIMEAttachment class does not support subcontainers by its very nature, all it must do is construct the proper headers for the segment and return them. Other than checking to ensure that a file indeed was loaded prior to sending the headers, this create() function also uses the PHP function pathinfo() to determine the base name (the name of the file without the path) of the file to be attached. This filename is then used in the headers as the filename parameter of the Content-Type header. When rendered, this will be the filename that is displayed in the email client as the name of the file. Because this is designed to be a file attachment and is separate from the actual email message, a Content-Disposition header is used to indicate to the email client that this is an attachment. Finally, the chunk_split() PHP function is used to divide up the base64-encoded file into 76-character chunks. This is done to conform to the RFC standard RFC 2045.

Like the MIMEAttachment class, the MIMEContent class is used to embed files into your email message. However, unlike the MIMEAttachment class, the MIMEContent class is used to include files as part of the multipart/related MIME type. That is, files included using this class are designed to be included and used as part of an HTML formatted email (or other relevant MIME type).

Unlike any other class I've discussed, the MIMEContent class is the first class not to inherit its methods and member variables from the MIMEContainer class. Rather, MIMEContent is an extension of the MIMEAttachment class. This class also adds two new methods, get_content_id() and set_content_id(), which are used to get and set the value used for the content-id, respectively. The entire class can be found in Listing 16.10.

Listing 16.10. The MIMEContent Class
<?php

class MIMEContent extends MIMEAttachment {

    protected $content_id;

    public function get_content_id() { return $this->content_id; }
    public function set_content_id($id) { $this->content_id =$id; }

    function __construct($file="", $mimetype="") {

        parent::__construct();
        $this->content_id = uniqid(rand(1,10000));

        if(!empty($file)) {
            $this->set_file($file, $mimetype);
        }

    }

    public function create() {

        if(!isset($this->content)) return;

        $addheaders = implode($this->add_header, "\r\n");
        $headers  = "Content-Type: {$this->content_type}\r\n";
        $headers .= "Content-Transfer-Encoding: {$this->content_enc}\r\n";
        $headers .= "Content-ID: {$this->content_id}\r\n$addheaders\r\n";
        $headers .= chunk_split($this->content)."\r\n";

        return $headers;

    }


}

?>

As you can see, the MIMEContent class's create() function is fairly straightforward. As per the specification described earlier in this chapter, the Content-ID header is used to provide the ID for the content.

The fifth and final class used in the generation of MIME messages is the MIMEMessage class. This class is a very simple class and is used to provide a means to include the "body" content of an email when sending a multipart email. It has only one real method, create(), which returns the appropriate headers. Also note that, as was the case with previous classes in this chapter, the MIMEMessage class also declares a dummy add_subcontainer() method to disable that functionality. See Listing 16.11 for the code.

NOTE

This class is not necessary when sending a simple email consisting of a single segment. In these cases, setting the content of the MIMEContainer class (using the set_content() method) is sufficient.


Listing 16.11. The MIMEMessage Class
<?php

    class MIMEmessage extends MIMEContainer {

        public function create() {


            $addheaders = (is_array($this->add_header)) ? implode($this->add_header, "\r
\n") : '';

            $headers  = "Content-Type: {$this->content_type}\r\n";
            $headers .= "Content-Transfer-Encoding: {$this->content_enc}\r\n$addheaders\r\n";
            $headers .= $this->content."\r\n";

            return $headers;

        }

    }

?>

    Team LiB
    Previous Section Next Section