Twilio's recently announced Picture Messaging has vastly opened up what we can do with text messaging, now we can attach photos to our text messages and have them get used in different ways.
In our case, we are going to build a Photo Tag Wall, which will contain photos linked to tags that will be displayed on a website.
This can be handy for events, or parties, or just about anything where you want to associate photos and tags.
To process our photos, we’ll be doing a few different things; We’re going to store them locally, and then resize them to display on our screens nicer.
We're going to use the Jolt Microframework for PHP, and Idiorm and Paris for our MySql handling.
Getting Started
Ok, first let's set up our database:
CREATE TABLE `tag`( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL DEFAULT '', `slug` varchar(255) NOT NULL DEFAULT '', PRIMARY KEY (`id`), KEY `name` (`name`), KEY `slug` (`slug`) ) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; CREATE TABLE `photo`( `id` bigint(20) NOT NULL AUTO_INCREMENT, `tag_id` bigint(20) NOT NULL DEFAULT '0', `file` varchar(255) NOT NULL DEFAULT '', `from` varchar(255) NOT NULL DEFAULT '', `country` varchar(255) NOT NULL DEFAULT '', `datetime` timestamp DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `tag_id` (`tag_id`), KEY `file` (`file`) ) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=latin1;
We're setting up two tables, one to store the tag, and one for the photos and the id to the tag they are associated with.
This table will store the tag, the image, and some meta data about the phone number that sent the photo.
We're also going to need to download the Jolt framework, the Twilio PHP Library, and Idiorm and Paris.
The first thing you'll want to do is grab these packages from their respective websites:
Now that you have all the packages downloaded to your computer, it's time to setup your directory structure. We'll be putting the files into our site's root folder.
We're putting the web services related files inside the Services
folder, since it helps us watch where things are.
Ok, let's set up our config.ini
file, open up config.ini
in your editor and modify the following settings:
;site settings site.name = my site site.url = http://mysiteurl.com ; rendering vars views.root = views views.layout = layout ; session vars cookies.secret = IeNj0yt0sQu33zeflUFfym0nk1e cookies.flash = _F ; db stuff db.enabled = true db.host = MY DATABASE HOST db.name = MY DATABASE NAME db.user = MY DATABASE USER db.pass = MY DATABASE PASSWORD ; twilio stuff twilio.accountsid = MY TWILIO ACCOUNT SID twilio.authtoken = MY TWILIO AUTH TOKEN twilio.from = MY TWILIO FROM NUMBER
You can see what you'll have to fill in here, your site name, and URL, your database info and your Twilio info.
Now for the Coding!
To get started, let's set up our models. We'll create a file inside the system
folder called models.php
:
<?php class Tag extends Model{ public function photos(){ return $this->has_many('Photo'); } } class Photo extends Model { public function tag(){ return $this->belongs_to('Tag'); } } ?>
This is a pretty basic model layout, but one nice thing about it, is that we're using Paris to establish a relationship with the tag
table. In fact, because we've previously built our database to have a tag_id
field in the photo
table, this model knows to associate all photos with the tag_id
, where tag_id
is the table name and the primary key field in the tag
table.
The same is true for the Photo
class, where we've set it to belong to a tag as specified in the tag()
function.
This is handy for building a quick model system without a lot of overhead.
We also want to create our functions.php
file, which we will also keep inside the system
folder:
<?php function slugify( $string ){ $string = strtolower( trim($string) ); $slug=preg_replace('/[^A-Za-z0-9-]+/', '-', $string); return $slug; } function cropResize($img,$out='',$dSize=170){ $x = @getimagesize($img); $sw = $x[0]; $sh = $x[1]; $yOff = 0; $xOff = 0; if($sw < $sh) { $scale = $dSize / $sw; $yOff = $sh/2 - $dSize/$scale/2; } else { $scale = $dSize / $sh; $xOff = $sw/2 - $dSize/$scale/2; } $im = @ImageCreateFromJPEG ($img) or // Read JPEG Image $im = @ImageCreateFromPNG ($img) or // or PNG Image $im = @ImageCreateFromGIF ($img) or // or GIF Image $im = false; // If image is not JPEG, PNG, or GIF if (!$im) { readfile ($img); } else { $thumb = @ImageCreateTrueColor ($dSize,$dSize); imagecopyresampled($thumb, $im, 0, 0, $xOff,$yOff, $dSize, $dSize, $dSize / $scale ,$dSize / $scale); } if( $out == '' ){ header('content-type:image/jpeg'); imagejpeg($thumb); }else{ imagejpeg($thumb, $out); } }
functions.php
will contain two core functions, one function, slugify()
, will convert tag names into slugs, and the cropResize()
function will take the image we pass to it, and save it within new dimensions.
We'll be using these functions quite a lot coming up.
Most of our code will be stored inside index.php
, so let's set up the bare bones for it:
<?php include("system/jolt.php"); require 'system/idiorm.php'; require 'system/paris.php'; require 'system/models.php'; require 'Services/Twilio.php'; require 'system/functions.php';
Ok, we've included our files, and nothing happened. Now, let's get Jolt up and running:
$app = new Jolt(); $app->option('source', 'config.ini');
The above code just sets up Jolt and tells it to read the config.ini
file and set our configuration settings, now let's connect to our database:
if( $app->option('db.enabled') != false ){ ORM::configure('mysql:host='.$app->option('db.host').';dbname='.$app->option('db.name')); ORM::configure('username', $app->option('db.user') ); ORM::configure('password', $app->option('db.pass') ); }
Our final piece of bootstrapping, we want to set up our Twilio client:
$client = new Services_Twilio($app->option('twilio.accountsid'), $app->option('twilio.authtoken') ); $fromNumber = $app->option('twilio.from'); $app->store('client',$client);
This is our bootstrap section, so far all we've done is included our files, set up our Jolt app, connected to our database and initialized our Twilio client.
Right now, if you run your app, you'll get a few errors. This is fine, we'll be taking care of those errors next.
Routing
Now we have to set up our routes and tell our app what to do based on certain rules. These rules will be either get
or post
.
Our initial rules will be the home page, the tag page, and the listener:
$app->get('/',function(){ $app = Jolt::getInstance(); }); $app->get('/tag/:tag',function($tag){ $app = Jolt::getInstance(); }); $app->post('/listener',function($tag){ $app = Jolt::getInstance(); }); $app->listen();
We've just set up the initial bare bones actions for our homepage, which is represented by the '/'
, our tag page, and our listener.
You'll notice the listener is a post
rather than a get
, that is because this is the handler from Twilio when new messages are received.
Lastly, you'll see the $app->listen();
method call. This is the most important method call we have, as it tells the app to start running.
There's No Place Like Home
Let's set up the home page, and build the view that we'll be displaying for everybody.
Replace the original homepage route with this one:
$app->get('/', function(){ $app = Jolt::getInstance(); $tags = Model::factory('Tag')->count(); if( isset($tags) ){ $images = Model::factory('Photo')->count(); $tagList = Model::factory('Tag')->find_many(); }else{ $tags = 0; $images = 0; $tagList = array(); } $app->render( 'home',array( 'tags'=>$tags, 'tagList' => $tagList, 'fromNumber' => $app->option('twilio.from'), 'images'=>$images )); });
You'll also notice that we tell it to render something called 'home
', in the views
folder, there is a home.php
file, open it up and edit it as follows:
<p >Text <span><?php echo $fromNumber ?></span> a picture with the name of a tag. Your image will be displayed on that tag.</p> <div> <div> <p>Number of Tags: <?php echo $tags; ?></p> <p>Number of Images: <?php echo $images; ?></p> </div> </div> <hr /> <h3>Tags</h3> <ul> <?php foreach($tagList as $tag){ ?> <li> <a href="<?php echo $uri?>/tag/<?php echo $tag->slug?>"><?php echo $tag->name?></a> </li> <?php } ?> </ul>
This file will take the variables we pass from the $app->render()
function and make use of them here.
We're going to display a count of total tags, along with total images, and a list of tags that a visitor can click on.
The actual page layout is controlled by a file called layout.php
. Let's go ahead and update that file now:
<html> <head> <title><?=$pageTitle?></title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet"> <link href="<?=$uri?>/style.css" rel="stylesheet"> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script> <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script> </head> <body> <div> <div> <ul> <li><a href="<?=$uri?>">Home</a></li> </ul> <h3>Photo Wall</h3> </div> <hr /> <section> <?=$pageContent?> </section> </div> </body> </html>
This is pretty bare bones HTML, but it covers what we're needing. All output gets sent to the $pageContent
variable in layout.php
.
Picture Messaging!
Ok, now let's handle the actual uploading of pictures from Twilio.
Log into your Twilio account and point a phone number to http://MYSITEURL/listener
for SMS messages, where MYSITEURL
is the address where you've uploaded your app.
We're going to replace our listener route with this one:
$app->post('/listener', function(){ $app = Jolt::getInstance(); if ( isset($_POST['NumMedia']) && $_POST['NumMedia'] > 0 ){ // let's find out what tag this is for.. or create a new one.. $thetag = slugify( $_POST['Body'] ); $tag = Model::factory('Tag')->where_equal( 'slug', $thetag )->find_one(); if( isset($tag->id) && !empty($tag->id) ){ $tag_id = $tag->id; }else{ // no tag already exists... $tag = Model::factory('Tag')->create(); $tag->name = $_POST['Body']; $tag->slug = slugify( $_POST['Body'] ); $tag->save(); $tag_id = $tag->id(); } for ($i = 0; $i < $_POST['NumMedia']; $i++){ if (strripos($_POST['MediaContentType'.$i], 'image') === False){ continue; } $file = sha1($_POST['MediaUrl'.$i]).'.jpg'; file_put_contents('images/original/'.$file, file_get_contents($_POST['MediaUrl'.$i])); chmod ('images/original/'.$file, 01777); // Edit image $in = 'images/original/'.$file; $out = 'images/processed/'.$file; cropResize($in,$out,250); chmod ('images/processed/'.$file, 01777); // Remove Original Image unlink('images/original/'.$file); $photo = Model::factory('Photo')->create(); $photo->tag_id = $tag_id; $photo->file = $file; $photo->from = $_POST['From']; $photo->country = $_POST['FromCountry']; $photo->save(); } $message = $app->store('client')->account->messages->sendMessage( $app->option('twilio.from'), // From a valid Twilio number $_POST['From'], // Text this number "Image(s) Added to <".strtolower(trim($_POST['Body']))."> Photo Wall Link: ".$app->option('site.url')."/tag/".strtolower(trim($_POST['Body'])) ); return true; }else{ if ( isset($_POST['From']) ){ $message = $app->store('client')->account->messages->sendMessage( $app->option('twilio.from'), // From a valid Twilio number $_POST['From'], // Text this number "MMS error. Please try sending your image again." ); } header('HTTP/1.1 400 Bad Request', true, 400); return false; } });
There is no view associated with this action. Now, let's go over what it does.
This is only called during a post
, hence the $app->post()
statement.
When it is activated by someone sending in a message, we check to see if there are any images attached, and if there are, then we cycle through them and save them in the database.
First, we check to see if there are any tags already stored in our database that match the tag we attached to our image, and if there is, then we grab the id
from the database, otherwise, we save a new record containing that tag's information.
Next, we cycle through the uploaded files and make sure they are images. Each image is downloaded locally and stored inside the images/original
folder. We then resize and crop the images to be a more manageable size, and store the new files inside the images/processed
folder.
Finally, we store the images inside the database, along with some meta data on the call itself, and send a text message back to the sender to tell him or her to check out the tag page.
If no images were attached, then we send them a message that there was an error.
The Photo Wall
Now, we've set up the home page, and we've set up the listener. What's left, is to set up the photo wall itself.
This will go inside the $app->get(‘/tag/:tag')
call.
Replace the original placeholder with the following code:
// preload photos whenever a matching route has :tag in it $app->filter('tag_slug', function ($tag_slug){ $app = Jolt::getInstance(); $tag = Model::factory('Tag')->where_equal('slug',$tag_slug)->find_one(); $photos = $tag->photos()->find_many(); $app->store('tag', $tag); $app->store('photos', $photos); }); $app->get('/tag/:tag_slug', function($tag_slug){ $app = Jolt::getInstance(); $tag = $app->store('tag'); $photos = $app->store('photos'); $app->render( 'gallery', array( "pageTitle"=>"viewing Photos for {$tag->name}", "tag"=>$tag, "photos"=>$photos )); });
Notice the $app->filter()
, This is a handy method we can set up that will grab the tag and its photos each time the $tag_slug
variable is passed, this lets us save on extra queries.
Now, we need to set up a gallery.php
page inside views
:
<div> <h1>#<?php echo $tag->name; ?></h1> </div> <hr /> <div> <div> <div id="container"> <?php foreach($photos as $photo){ ?> <?php if (file_exists('images/processed/'.$photo->file)){ ?> <div> <div> <a href="<?php echo $uri?>/images/processed/<?php echo $photo->file ?>" title="<?php echo $photo->datetime ?>" > <img src="<?php echo $uri?>/images/processed/<?php echo $photo->file ?>" /></a> <p><?php echo $photo->datetime?></p> </div> </div> <?php } ?> <?php } ?> </div> </div> </div> <script src="//cdnjs.cloudflare.com/ajax/libs/masonry/3.1.1/masonry.pkgd.min.js"></script> <script type="text/javascript"> var container = document.querySelector('#container'); var msnry = new Masonry( container, { itemSelector: '.image' }); </script>
This will display the gallery, and use jQuery masonry to float all the images nicely.
In Conclusion
So that completes our app. You've now built a handy little photo wall that can be used to show photos from events. Be sure to checkout the links provided above to learn more about the libraries and frameworks used throughout this article. Thanks for reading.
Comments