Posts from March 2007

  • PHP code for validating/filtering HTML input.
    filed under: php, programming, security

OTFG Housekeeping

This afternoon I threw all of the code I've written for my photoblog into one directory: onfocus photos code. I realized all of the SQL stuff is spread across several text files, the code is scattered across a dozen posts, and I just need a place to dump stuff as I update the site. I'm going to try to keep this directory updated as I go. I should probably use subversion, and offer it all as a .zip file, but it's really not at a public consumption stage yet.

I spruced up the standard Apache indexes with Silk Icons by Mark James. Thanks!

OTFG: Syndication, Exif Data, and Tags

Now that I'm off the photo-hosting grid and have my local photoblog up and running, I've been working on some of the extra features. This week I set up an RSS feed and JavaScript syndication, grabbed Exif data from my photos, and set up a way to browse photos by tag. I won't go into great detail about my thinking on these—I'll just share my code and describe how I set up each one briefly.
Syndication
The RSS feed was fairly easy to put together. Here's the code that generates the feed: rss.php. I added a few extra constants to the ini.inc file that's included on every page because they were needed in the feed: APP_TITLE, APP_DESCRIPTION, BASE_URL, PHOTO_URL, and PHOTOGRAPHER_NAME. I also went back through each file in the application and substituted these constants where I'd hard-coded the values. (This will make it easier to change these variables globally in the future, and it makes the code more reusable.) And I went with full-sized images in the feed. Here's the live feed if you'd like to check it out.

One of Flickr's nice features that I was using before my move was their JavaScript badges. With a few lines of CSS and a single line of JavaScript, you can include a strip of photos on any website. I was using a vertical strip of five photos (check "now seeing" in the right sidebar on the front page of this site for an example). I decided to throw this together for my local photoblog, and here's the file that generates the JavaScript that generates the strip: js.php. Nothing too tricky going on here, just a document.write of some HTML that displays the smaller-sized thumbnails of recent photos.

I store both rss.php and js.php outside of my public web directory. I run them every hour, piping the output to a couple of files in the public web directory. To accomplish this with Windows Task Scheduler, I have a simple .bat file that looks like this:

C:\path\to\php\php-win.exe C:\scripts\js.php > C:\web\site\include.js
C:\path\to\php\php-win.exe C:\scripts\rss.php > C:\web\site\rss.xml


So I just run this batch file every hour and that way the feed and JavaScript file are cached and won't be re-built each time someone requests it.

I set up an .htaccess entry for the feed so it has a nicer URL: RewriteRule ^feed/?$ rss.xml [L]. I'm the only one consuming the JavaScript, so that URL can be as ugly as it wants to be.
Exif Data
Most (if not all) digital cameras embed some information about the state of the camera within the photo files themselves using a format called EXIF. Flickr made great use of this data with a special page that displays quite a bit of the Exif data available in uploaded photos. (For example, More detail about bandon beach.) The Exif data gives you a quick look at your shutter speed, aperture, focal length, and a few other settings. I think it helps to look at Exif data frequently so I can remind myself which settings worked or didn't work for any particular type of photo.

Because the Exif data is embedded directly in the original photographs, I could just grab this info on-the-fly to display it on the site. (PHP 5 has a few Exif functions that make snagging this data fairly easy.) But I'm guessing down the road I'd like to do more with this data, such as grouping all photos by a particular camera together, or grouping all photos taken with my 50mm lens together. More control means storing everything in the database, and I put together a table called exif and a generic function to grab/store the data.

Here's the SQL to build the table: otfg_tables_6.txt, and here's the include file with the exif-grabbing function: addExif.inc. The function addExif() accepts a PhotoID and database connection, finds the original file for that photo, and extracts and saves the Exif data. I went back and added this function to the uploading files (Step 10: Adding Photos), and whipped up a quick script to add Exif data for existing photos: setExif.php.

After running this script I had a table full of Exif data, and I found out that 490 of my 840 photos had Exif data. Unfortunately my cell phone strips Exif data from photos before it sends them via email, which means my cell phone pictures (a large percentage) don't have any Exif data to extract. One improvement to this process that I need to write soon is setting the DateTaken value in the photos table based on the Exif DateTimeOriginal value. That way I can display both the time the photo was posted, and the time the photo was actually taken.

I'm displaying the Exif data on the photo detail page, as a single line of text. For example, the photo bandon beach shows the following Exif line below the photo:

otfg exif

This Exif line lists the camera make and model, the shutter speed, the aperture, and the focal length. I'll probably add ISO and exposure bias eventually because I like to see that data too, but I thought these were the basics.
Tags
I've also added some features around tagging. Each tag listed on the photo detail page is now linked to a tag page where viewers can see all photos with that particular tag. One example is the tag page for architecture—a tag I've used frequently. Here's the code for this page: tag.php. As viewers click photos from the tag page, they're able to stay within that tag context. So all of the back/next controls on the photo detail page reflect the viewer's most recent choice. The photo detail URL is also under the /tag directory to reflect the different context. (I've set up my robots.txt to ask robots to ignore the /tag directory so there aren't multiple locations for photo detail pages.)

Today I put together a standard tag cloud so I can visualize how I'm using tags. As usual, the larger the tag, the more frequently it's used, with red tags the most-used. Here's the code for this: tags.php.

All of this tagwork required some significant changes to photo.php and editing.js. Both of these updated scripts are getting more complex by the hour. (Beyond the tag stuff, I also improved some other JavaScript editing functions so simple HTML won't wreak havoc in captions.) I also added a few more entries to the .htaccess file to handle this new tag-space:

RewriteRule ^tag/(.*?)/(\d{1,2})/$ tag.php?tag=$1&page=$2 [L]
RewriteRule ^tag/(.*?)/(\d{4}/\d{2}/.*)$ photo.php?p=$2&tag=$1 [L]
RewriteRule ^tag/(.*?)/$ tag.php?tag=$1 [L]


This just cleans up the URLs for tag pages, and paging through photos there. As you can see, photo stubs under /tag/ are just routed back to photo.php along with the tag itself. (This is also some good foreshadowing for how I'll probably handle galleries since these are basically just tag-galleries.)

All in all, going off the grid is going well. I still have a long to-do list, but I feel like I crossed some of the big features and fixes off my list this week.

OTFG Step 11: Displaying and Editing Photos

Good morning, off the grid fans! I know it's been a while—I've been working on my new photoblog behind the scenes. I'm at a point where I'm not sure it helps anyone to share my code, but who knows? Last time I talked about how I'm getting photos into my system, and today I'll explain how I'm editing photos that are already in the system.
Photo URLs
The first thing I needed to do was provide a permanent home for every photo that finds its way into my database. The easiest method would be using the internal photo ID in the URL somehow, so I'd end up with a URL for any photo like http://photos.onfocus.com/459918/. But as I mentioned when designing the photo file locations (Thinking about Photo URLs), I want to include a bit more information about the photo in the URL. So I went with a pattern similar to the files: http://photos.onfocus.com/[year]/[month]/[title]. As with files, the title is stripped of any non-URL-approved characters, and whitespace is replaced with a dash.

I also wanted to keep photo IDs internal, and not use them anywhere within the public-facing application. So I thought the URL pattern of [year]/[month]/[title] would be a good way to uniquely identify a photo within the system as long as there were no duplicates. To make this happen I set up a field in the database called Stub (varchar(50)) that holds the entire string: [year]/[month]/[title]. If two photos have the same title within the same year and month, I simply increment the stub like this: [year]/[month]/[title]-1, [year]/[month]/[title]-2, etc. This way the permalink can be used not only to see the photo on the Web, but also to identify the photo within the application.

I wrote a quick script to add URL stubs to all of the existing photos: addStubs.php. And I retrofitted all of the uploading scripts from the last step to write a URL stub as a photo is added.

With the virtual space for photos set to go, I needed to give them a permanent home. I set up a script called photo.php that shows one photo at a time, along with a bunch of details about that photo. By passing a URL stub into the script like this http://photos.onfocus.com/photo.php?p=[photo stub], the page knows which photo to display. With a little .htaccess magic: RewriteRule ^(\d{4}/\d{2}/.*)$ photo.php?p=$1 [L] the nicely formatted URLs are a reality: for example, bandon beach.
Photo Detail Page
So the photo detail page accepts an incoming URL stub, looks up info about that photo in the database, and arranges things nicely on the page. Here's what a photo detail page looks like in my system today:

otfg photo detail

The main bits are the title, photo itself, caption, time the photo was posted, and a list of tags associated with the photo. This is the public view. If you have the right credentials (set up in Step 9: Authentication), you see a bit more on the page, and you have a few more options. As an administrator, directly below the caption is a row of administrative buttons:

otfg admin buttons

Here's what each button does:
  • Sets the public/private status of the photo.
  • Toggles the caption editing form.
  • Rotates the photo 90 degrees clockwise.
  • Completely removes the photo, its thumbnails, and all associated info.
The other thing an admin can do is edit the title, caption, and tags in place by clicking on any of these things (like Flickr). It looks like this:

otfg editing title

All of this editing is accomplished with a series of files. The Ajax package Prototype handles some of the work in the background. And the rest of the interface stuff is in a Javascript file that's included on the page for an administrator: editing.js. The functions in this file post to several PHP scripts that return simple text information: The only function that requires a page refresh is completely deleting a photo, which is ok with me because the photo detail page is about to be history anyway and I've got to go somewhere. The administrative buttons all have a JavaScript confirmation dialog before they execute, so I can't accidentally delete a photo when I want to rotate it.

And here's the beast of a script that pulls everything together: photo.php. This is fairly complex, especially because the public design and administrative functions live together in the same page.
Photo Home Page
With the photo detail page set, I set up a place to introduce people to my photos: the home page. Again, it's very Flickrish, with two columns of photos and a list of pages at the bottom. The only difference is that the latest photo is at its largest size at the top. I haven't built any editing into this page yet, so any updates have to happen on photo detail pages. And here's the code that powers the front page: home.php. I set up an .htaccess rule for this page too, to help with paging: ^home/(\d{1,2})/?$ home.php?page=$1 [L]. That way, as you page through the photos, you'll get friendly URLs like this: http://photos.onfocus.com/home/2.

I think this step gives me a functioning system I can use to publish photos. There are definitely more features I need to build: browsing by tag, extracting EXIF data, an RSS feed, galleries/sets, commenting, and mapping photos with coordinates. And I have some issues I need to work out. (Anyone know how to force a cache refresh on photos after rotation beyond adding some random numbers to the file name?) But I think all of the bare essentials for sharing photos with the world are working now. woohoo! You can compare my local photostream to my Flickr photostream to see how similar they are.

By the way, the little administrative and tag icons are all modified from a set of tiny icons by Timothy Groves: Ho!Ho!Ho!. Thanks!