Tech Stuff

Gallery2 -> Zenfolio Migration

The last five years we’ve been running an installation of Gallery2 here at The Office, an open-source LAMP product that lets you manage and display your photographs. It’s a big and complicated piece of work that we felt was held together with bent coat hangers and duct tape. Every year or so we’ll get the white screen of death because someone tried to attach the wrong kind of thumbnail to an image, and we’d have to go back to the latest database backup and restore it.

So imagine my horror when I found out (and yes I should be more on top of these things) that development officially ended some months ago. I guess I can forget about getting the patches I was hoping for.

As luck would have it (and luck can sometimes favor the procrastinator) we were already shopping around for a single store for all our Gallery2, Flickr, hard-drive-backup, DVD and wherever-else photo sources. Zenfolio seemed to have most of what we need.

Now manually downloading-and-uploading 3300 image files isn’t my idea of a good time. Especially not after we spent a whole summer having interns tagging the things properly. Recreating all of that seemed like a loser of an idea. So I rolled up my sleeves and started digging into the APIs for Gallery2 and Zenfolio.

The Gallery2 API is huge and inscrutable. Like many such libraries it seems (to me) that you’ve got to understand everything before you understand anything. But I did know what I wanted to do moving from one to the other:

  1. Recreate each gallery and subgallery
  2. Grab each full-size image and its associated meta-data from the old service
  3. Recreate it in the correct place on the new service

Since I have access to the filesystem and the database, I decided to dispense with the Gallery2 API altogether and just write my own queries. That was a pretty simple tasks, all things considered, once I sorted out what was where and where the keys were. Two queries looping together were basically enough to get it done.

Zenfolio though would definitely require their API. To say that the Zenfolio API is little-understood would be to understate. String Theory is little-understood. It might be fairer to say nobody understands the Zenfolio API. Just look at the traffic on their support forums. There isn’t any. It took days to get a simple reply from their tech people. By then I’d closed in on the solution without their help.

Here’s the basic process for it, and a zip file of the PHP source code. You’ll need the phpZenfolio wrapper to include in your code. I had to make a couple of changes (explained in the comments) to extend the timeout from 30 seconds to 90; and to change the mb_string() function call to string(), because of support issues.


Call in the phpZenfolio.php library, and set up the basic constants. You can copy the Gallery2 ones from your config file. And you’ll also need the root-level logon and password for your Zenfolio account (a secondary user account won’t work).

Gallery2 Data

The query you’ll find on line 80 finds the root-level gallery ID of your installation. It won’t necessarily be 0 or 1, if you’ve shuffled things around, started out with test galleries you later deleted, or whatever. Then it calls a recursive function to select the relevant information about each gallery and sub-gallery in your site. That incldes titles, summaries, keywords, file paths, descriptions as well as a count of loose photos within.

Looping through the query, if it finds a gallery with sub-galleries, it creates a corresponding gallery on Zenfolio, then recurses.

xx_Item.g_id ID, xx_Item.g_title title, xx_Item.g_description description,
xx_Item.g_summary summary, xx_Item.g_keywords keywords,
xx_Item.g_summary summary, xx_FileSystemEntity.g_pathComponent path,
LEFT JOIN xx_Entity ON xx_Item.g_id=xx_Entity.g_id
LEFT JOIN xx_ChildEntity ON xx_Item.g_id=xx_ChildEntity.g_id
WHERE xx_Entity.g_entityType="GalleryPhotoItem"
AND xx_ChildEntity.g_parentId=ID) loosies
FROM xx_Item
LEFT JOIN xx_ChildEntity
ON xx_Item.g_id=xx_ChildEntity.g_id
LEFT JOIN xx_FileSystemEntity
ON xx_Item.g_id=xx_FileSystemEntity.g_id
LEFT JOIN xx_Entity
ON xx_Item.g_id=xx_Entity.g_id
WHERE g_canContainChildren=1
AND g_parentId=10553
ORDER BY xx_Item.g_id ASC

One piece of information I got from tech support at Zenfolio turned out to be questionable. I was asking about the difference between Galleries and Collections, and was told that Galleries contain images, but Collections contain other Collections or Galleries and not images. Turns out that’s not true, Collections just contain links to other resources; Groups contain other Galleries. And they may well be allowed to hold images as well; but by the time I’d figured this out I had normalized all my galleries so they only contains images or subgalleries, but not both.

If it finds loose photos, it gather the information about them, including the filesystem path, and uploads it to Zenfolio.

xx_ChildEntity.g_parentId, xx_Item.g_id, xx_Item.g_title,
xx_Item.g_description, xx_FileSystemEntity.g_pathComponent,
xx_DataItem.g_mimeType, xx_Item.g_keywords, xx_Item.g_summary,
xx_Entity.g_entityType, xx_Entity.g_modificationTimestamp, GROUP_CONCAT(xx_TagMap.g_tagName SEPARATOR ',') TAGS
FROM xx_ChildEntity
ON xx_ChildEntity.g_id=g2_Item.g_id
LEFT JOIN xx_DataItem
ON xx_ChildEntity.g_id=xx_DataItem.g_id
LEFT JOIN xx_Entity
ON xx_ChildEntity.g_id=xx_Entity.g_id
LEFT JOIN xx_FileSystemEntity
ON xx_ChildEntity.g_id=xx_FileSystemEntity.g_id
LEFT JOIN xx_TagItemMap
ON xx_ChildEntity.g_id=xx_TagItemMap.g_itemId
ON xx_TagItemMap.g_tagId=xx_TagMap.g_tagId
WHERE xx_ChildEntity.g_parentId=23
AND xx_Entity.g_entityType='GalleryPhotoItem'
GROUP BY xx_Item.g_id

These are big clunky queries and I have no idea how efficient they are. But you should only need to run this once anyway, so why worry?

Important Differences

  • A Gallery2 gallery can have a title, summary, description and keywords.
  • A Zenfolio gallery can have a title, caption, keywords, category and sub-category.
  • A Gallery2 image has a title, summary, description, keywords and tags.
  • A Zenfolio images has a title, caption, keywords, category and sub-category, copyright and an alt-attribute.

So I’ve included steps to combine and de-dupe tags and keywords, and combine summaries and descriptions into one block of text.

A Few Warnings

Learn from my errors, little ones. I spent a long time on this. I’ve added code that will create a couple of log files for you, so you can copy-and-paste queries into phpMyAdmin or whatever, or see which functions failed when. “logfile.txt” contains all the queries, as well as php serializations of the updater arrays. That’s where the long queries above come from.

“funfile.txt” shows literals of how the API calls look before they’re executed. You’ll information like this:
$newphoto = $f->upload("PhotoSetId=12345678", "File=/var/wherever/g2data/albums/galleryname/subgalleryname/filename.jpg","modified=Mon, 02 Jul 2012 14:41:06 -0400")

Metadata: a:3:{s:5:"Title";s:17:"Law School Atrium";s:7:"Caption";s:0:"";s:8:"Keywords";a:4:{i:0;s:17:"Law School Atrium";i:1;s:10:"law school";i:2;s:7:"October";i:3;s:4:"2007";}}
$photupdater = $f->UpdatePhoto($newphoto, $newphotoinfo)

UpdatePhoto() will fail for no apparent reason. My logs show consecutive lines that are almost identical, a “4” becomes a “5”, but one will throw an “unknown error” and the other will work. From the logs, it might be possible to re-attempt these later on.

Use the root Zenfolio account logon.

Use CreatePhotoSet(1234,”Gallery”,$updater) to make a holder for images.

Use CreateGroup(1234, $updater) to make a holder for other PhotoSets.

Don’t pass undeclared or null variables or arrays into any of the Zenfolio API function calls.


Once I had my tests complete and “pushed the button,” it took 8-10 hours to finish all 3300 images. Some were fairly big, but it handled all the files including TIFF and PDF files.

Tom McGee has been building web sites since 1995, and blogging here since 2006. Currently a senior developer at Seton Hall University, he's also a freelance web programmer and musician. Contact him if you have the need for a blog, web site, redesign or custom programming!

Leave a Reply

Your email address will not be published. Required fields are marked *