Creating a Mobile Photo Blog, Part 1

Tweet
This entry is part 1 of 2 in the series Creating a Mobile Photo Blog

Creating a Mobile Photo Blog

It seems like everyone these days is texting away on their mobile phone or updating their social network status every 5 minutes. It’s no surprise that the convenience of being able to access the Internet from anywhere at any time has made sharing messages and pictures so popular. I can’t imagine going anywhere without my cell phone on the off chance that something interesting might happen and I can document it as if I were the first news reporter on the scene.

This is the first article in a two-part series in which I will show you how to create a photo blog as part of your personal website which you can update from your phone simply by sending an email. You’ll write a script to check the inbox of an email account for new messages using POP3; the script will extract the messages’ subject line, body text, and attachments and update a database accordingly. You can then pull the information from the database for display on your blog, in a sidebar, or however else you see fit.

Rather than developing a mobile application such as for Android or iPhone, the code will run on a web server and in effect be platform independent. With regard to PHP, I will be using the IMAP and Imagick extensions so you’ll want to make sure they are installed before you get started.

Security Considerations

For security reasons, you will obviously need some type of authentication. To achieve this you first need to setup a private email account for the web server that only you know about. This is the account that will receive the updates. Then, you will need a second email account for your mobile device to send updates. I’ll be using the private email not@liberty2.say and the public email mobile@example.com in the code throughout the articles.

The level of security you can apply to the server email account will vary depending on what administrative privileges you have. In a best-case scenario, you want your mail server to reject all incoming mail on the account except from your mobile’s public address. In a worst-case scenario, you want to at least make sure its spam filter is set to the highest possible setting and add your mobile address to its whitelist.

As an added layer of security, every email received by not@liberty2.say will be assigned a unique token. The token will then be sent to your phone along with a brief summary of the message and ask you for approval. To update your blog, you’d simply send an email, wait for the auto-reply and then open the provided link to approve the update. The auto-reply will be sent by a script that executes every few minutes as a cron job or, if you have access to the mail server itself, you can opt to use something like procmail or maildrop.

With these security measures in place, you can be assured that nothing will ever be published without your consent.

Database

You need at least three tables for this project: the blog_posts table which will store a blog id, blog title, body text and a timestamp; the images table which will store an image id, a reference to a blog id, and a file path where the image is stored on the server; and the pending table which will store a message id referring to the message number in the inbox, a unique 32-character MD5 token, and a flag to indicate the approval status.

These are the CREATE TABLE statements for the database:

CREATE TABLE blog_posts (
  post_id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
  title VARCHAR(100) NOT NULL,
  body TEXT NOT NULL,
  create_ts TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,

  PRIMARY KEY (post_id)
);

CREATE TABLE images (
  image_id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
  post_id INTEGER UNSIGNED NOT NULL,
  image_path VARCHAR(200) NOT NULL,

  PRIMARY KEY (image_id),
  FOREIGN KEY (post_id) REFERENCES blog_posts(post_id)
);

CREATE TABLE pending (
  message_id INTEGER UNSIGNED NOT NULL,
  token CHAR(32) NOT NULL,
  is_valid ENUM('Y','N') DEFAULT 'N',

  PRIMARY KEY (message_id)
);

Retrieving Email

When it comes to retrieving email, you have the option of connecting with IMAP or POP3. The main difference between the two is in the method of mail storage. POP3 expects you to store mail on your own computer, so it only offers a way for you to download new mail and then the server’s copy is deleted. IMAP on the other hand is more like “web mail” in the sense that all the mail is stored and retained on the server which you access by connecting to remotely, and so it provides additional functionality such as folder management. IMAP is understandably more complex, which you can see for yourself by comparing the RFCs for the two protocols.

For all intents and purposes, POP3 will do the trick since you have no need for any special functionality. PHP provides functions for working with email protocols, including POP3, with its IMAP extension.

Documentation on many of the extension’s functions is spotty, and because of that they’re not easy to use. Luckily a set of wrapper functions were written and posted in the user contributed notes by Wil Barath which provide an API that’s much easier to use. The following class is based on Barath’s code and should be copied and saved as POP3.php:

<?php
class POP3
{
    private $conn;

    // make connection
    public function __construct($host, $user, $pass, $folder = "INBOX", $port = 110, $useSSL = false) {
        $ssl = ($useSSL) ? "" : "/novalidate-cert";
        $mailbox = sprintf("{%s:%d/pop3%s}%s", $host, $port, $ssl, $folder);
        $this->conn = imap_open($mailbox, $user, $pass);
    }

    // close connection and trigger expunge
    public function __destruct() {
        imap_close($this->conn, CL_EXPUNGE);
    }

    // retrieve a list of messages
    public function listMessages($msgNum = "") {
        $msgList = array();
        if ($msgNum) {
            $range = $msgNum;
        }
        else {
            $info = imap_check($this->conn);
            $range = "1:" . $info->Nmsgs;
        }
        $response = imap_fetch_overview($this->conn, $range);
        foreach ($response as $msg) {
            $msgList[$msg->msgno] = (array)$msg;
        }
        return $msgList;
    }

    // delete a message
    public function deleteMessage($msgNum) {
        return imap_delete($this->conn, $msgNum);
    }

    // parse headers into usable code
    public function parseHeaders($headers) {
        $headers = preg_replace('/rns+/m', "", $headers);
        preg_match_all('/([^: ]+): (.+?(?:rns(?:.+?))*)?rn/m',
            $headers, $matches);
        foreach ($matches[1] as $key => $value) {
            $result[$value] = $matches[2][$key];
        }
        return $result;
    }

    // separate MIME types
    public function mimeToArray($msgNum, $parseHeaders = false) {
        $mail = imap_fetchstructure($this->conn, $msgNum);
        $mail = $this->getParts($msgNum, $mail, 0);
        if ($parseHeaders) {
            $mail[0]["parsed"] = $this->parseHeaders($mail[0]["data"]);
        }
        return $mail;
    }

    // separate mail parts
    public function getParts($msgNum, $part, $prefix) {
        $attachments = array();
        $attachments[$prefix] = $this->decodePart($msgNum, $part, $prefix);

        // multi-part
        if (isset($part->parts)) {
            $prefix = ($prefix) ? $prefix . "." : "";
            foreach ($part->parts as $number => $subpart) {
                $attachments = array_merge($attachments, $this->getParts($msgNum, $subpart, $prefix . ($number + 1)));
            }
        }
        return $attachments;
    }

    // fetch the body of an email with one part
    public function fetchBody($msgNum = "") {
        return imap_fetchbody($this->conn, $msgNum, 1);
    }

    // decode attachments
    public function decodePart($msgNum, $part, $prefix) {
        $attachment = array();

        if ($part->ifdparameters) {
            foreach ($part->dparameters as $obj) {
                $attachment[strtolower($obj->attribute)] = $obj->value;
                if (strtolower($obj->attribute) == "filename") {
                    $attachment["is_attachment"] = true;
                    $attachment["filename"] = $obj->value;
                }
            }
        }

        if ($part->ifparameters) {
            foreach ($part->parameters as $obj) {
                $attachment[strtolower($obj->attribute)] = $obj->value;
                if (strtolower($obj->attribute) == "name") {
                    $attachment["is_attachment"] = true;
                    $attachment["name"] = $obj->value;
                }
            }
        }

        $attachment["data"] = imap_fetchbody($this->conn, $msgNum, $prefix);
        // 3 is base64
        if ($part->encoding == 3) {
            $attachment["data"] = base64_decode($attachment["data"]);
        }
        // 4 is quoted-printable
        elseif ($part->encoding == 4) {
            $attachment["data"] = quoted_printable_decode($attachment["data"]);
        }
        return($attachment);
    }
}

The class’ constructor establishes a connection to a mail server. The option of using SSL encryption is also available if you have a certificate. The destructor closes the connection.

The listMessages() method returns an array containing a list of all messages currently in the inbox, or you can optionally request information for one specific message by providing a message ID as an argument. The list returned doesn’t contain the actual contents of the email, but rather the from address, to address, subject line, and date.

The deleteMessage() method marks a message for deletion. The message will not actually be deleted until the connection is closed with the appropriate flag, which is done in the class’ destructor.

The parseHeaders() method uses regular expressions to parse the email headers which appear in the email as a single block of text, and returns the information as an array for easy access.

The mimeToArray() method separates each MIME type found and returns the information as an array. If multiple MIME types are found, then it’s a good indication that there are attachments.

The getParts() method extracts the different parts of the email such as the body and attachment information and returns the information as an array.

The fetchBody() method returns the body of an e-mail that has only one part, or no attachments.

The decodeParts() method takes the base64 or quoted-printable encoded attachment data, decodes it, and returns it so you can do something useful with it.

With this class, you can now connect to the server and retrieve a complete list of messages in the inbox as follows.

<?php
require_once "POP3.php";

define("EMAIL_HOST", "pop.liberty2.say");
define("EMAIL_USER", "not@liberty2.say");
define("EMAIL_PASSWD", "********");

$pop3 = new POP3(EMAIL_HOST, EMAIL_USER, EMAIL_PASSWD);
$msgList = $pop3->listMessages();

foreach ($msgList as $msg) {
    echo "<pre>" . print_r($msg, true) . "</pre>";
}

Summary

This brings us to the end of Part 1. So far you have a game-plan for building the application and ensuring that only the messages you send are published. You also have the database schema and a class that allows you to connect to a POP3 server and retrieve messages. Be sure to come back for Part 2 to see how these pieces fit together to realize the project.

Image via Angela Waye / Shutterstock

Creating a Mobile Photo Blog

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • ben

    Nice article Martin. Can’t wait for the 2nd part. Cheers…

  • David Golding

    thank you and looking forward to part2!!

  • http://raghavendra-mobilephotography.blogspot.com/ raghavendra

    wow, even though blogger has announced dynamic views,
    Your post is way better than that.
    I too have a mobile photography blog,
    waiting for the second part!

  • Adam Hardtke

    Awesome post, can’t wait for part 2! However be sure to fix the order of your arguments when instantiating the POP3 class to match the constructor’s order.

    • http://zaemis.blogspot.com Timothy Boronczyk

      Oops! Thankfully it was just a small mistake here and I’ve fixed it. The example code pushed to GitHub was correct. Thanks for catching the issue, Adam!

  • http://WebsiteURL amran

    amazing!!!!!!!!!!!!!!!!!!!

  • Franklon

    It would be good, if demo is included

    • http://zaemis.blogspot.com Timothy Boronczyk

      @Franklon: There is a link in Part 2 to the demo code that has been uploaded to PHPMaster’s GitHub account.

  • http://WebsiteURL shore tours in St.Petersburg

    Sometimes your blog is loading slowly, better find a better host..-,~

  • http://www.gomobiles.com.au/ Koby Rennie

    Good work Martin….
    Every thing is explained in detail that also with examples….
    Simply great work!!!!