Writing a Plugin for Storytlr

January 19, 2010

Update: 2010-02-18
I put this onto the Storytlr wiki a while back. I highly recommend viewing that version instead of this one. This version will not be updated further.


Update: 2010-01-19
Edited in a minor fix, I learned a bit more when I wrote my foursquare plugin.
The correct revision to follow along with is now 75c520df.

I recently got interested in the lifestream application Storytlr.

One of the first things I wanted to do was add a plugin for github, as that is a fairly large part of my online life. Unfortunately there is very little documentation, which is understandable for a private project that just went open.

As such I had to work through some code, but all in all it wasn’t too tough. The source is very readable, just not set up with any auto-documentation.

So, as a way of giving back, I’m going to walk through the Storytlr plugin system, as I understand it. If you want to play along at home, you can grab the source from my github, and the commit we will be working from is 23136196

First things first, we need to copy off another plugin, that’s the easy way to get started. I cloned the RSS one, but they are all pretty similar to start with. Here’s what my final file tree looks like, it’s pretty similar to the starting tree, just with “Github” instead of “RSS”. This would reside at path_to_app/protected/application/plugins/github

So what are each of these and what do we have to do to them? Well, I like to drive my development by my data structures, so let’s start with database.sql. This is pretty straightforward SQL, and I’ll highlight some key fields after you have a look.


So what are our key items to look at here? Well, how about table naming convention? The existing tables are named as their plugin name, all lowercase, then _data. Why get fancy? Let’s use the convention.

What else is important? I kept the id and source_id pieces intact. These are actually fairly important, as I learned when I tried to make source_id into a varchar. source_id is actually the key that is tied to the sources table. That seems obvious now, but trust me, it didn’t when I was writing this the first time.

Aside from that, there isn’t too much to worry about. Just make sure to add something that you can use to identify individual updates on, github_id in this case, and then add your data fields and set your indexes. The naming of the FULLTEXT "SEARCH" key is probably important, though I didn’t test it any other way. You’ll probably find that all of your plugin tables will look more or less alike.

Now what? Well, let’s go ahead and define the model for an item, since we are already thinking about the data types. Here is a partial listing of GithubItem.php, have a look.

This is a very stripped down implementation. All of your data is available to the views through the inherited SourceItem::toArray method, so you really only need to override a few methods and put in any mangle logic, as I did in the getTitle method.

One other method I was sure to override was getType. The types are enumerated in /protected/application/admin/models/SourceItem.php, and are as follows:

I am not sure how they play into the rendering process at this point, but better safe than sorry, right?

Let’s move on to the views. These are all very similar, and are only rendered in different, well, views. I’ll go over timeline.phtml, but if you copy the others you should be able to piece it together in a jiffy.

Pretty brutal, huh? You just get your title (mangled by GithubItem::getTitle in this case) and get your content, and you are as good as done.

Finally, I’m going to address the engine that drives all of this, GithubModel.php. A lot of this is just editing boilerplate, so let’s start with that. Comments are added in-line, but aren’t in the git source.

Okay, now we are ready to dig into the meaty part, the data update functions. This is split into two parts, updateData and processItems. Here is a (hopefully) self-explanatory updateData implementation, more or less cloned from existing plugins.

Pretty easy, right? Just acquire the content (any way you wish, this one uses cURL), parse it out into an array and pass it on. If all goes well in processItems, then mark it as a successful update with SourceModel::markUpdated. Let’s see what processItems does.

Again, pretty simple. We take the passed in items, mangle them as needed and stuff them into a data array for storage. About the only unknown there is the addItem call, which is in the SourceModel class. Let’s take a look at the first part of that to understand our parameters.


The most important thing for us to note are the names of the arguments: $data, $timestamp, $type, $tags, $location, $hidden, $title. These are self-explanatory and help us understand why the existing plugins pass what they do. Some other pieces to note is the override of source_id on line 169, and how it builds the query from your $data arguments, on lines 174-183. Naming matters!

So, now we are back to GithubModel, and we just have a few more methods to go. What remains below are the form generators and processing for the admin interface. Github only needs one piece of information to work, the username, so that’s all we are asking for below.


getConfigForm creates the form fields using the Stuffpress_Form class, which is described in /protected/library/Stuffpress/Form.php. Github only has the one element, so it is added and set to required, then we are done.

processConfigForm is similarly simple, we get the value of our field, then make sure it is valid. If it is, we save it into our model’s properties, which is a SourcesProperties class, which is in turn a Stuffpress_Db_Properties class. Essentially think of it as a persistent key/value store. Or don’t think about it at all, just use it.

At this point you should have a working plugin!


Debugging Storytlr can be tough sometimes, so make sure your config file has debug = 1, and keep an eye on /protected/logs/.

If you have any questions, comments or corrections, please let me know!

Categories: Geek
Tags: , ,


  1. Thanks for the writeup. I’m planning on making a few plugins myself.

  2. john says:

    Cool, hope it helps!

  3. I got a fair way into integrating Zend Captcha but it doesn’t work for some reason. I’m not very good at PHP so I’m not surprised. I still have the code but I need to get a new PSU for my development server before I go hacking again.

    John, there something about storytlr that just makes you interested isn’t there? Not sure what it is but I really like storytlr even though it is missing some very important features.

    I am a better administrator than I am a developer so maybe we can set up public instance of storytlr based on your branch sometime.

  4. john says:

    Hey Daniel, do you have a github or anything like that? If you get one and put your source in there I can clone it and use it. Even better, fork my branch and then I can pull in your patches. Eventually (I hope) it will all get pulled back into the core.

    There definitely is something special about Storytlr, I just like being in the code and I like the way it pulls together all of my actions on the web for the big picture.

    Feel free to shoot me an e-mail and maybe we can set something up.

  5. Daniel Devine says:

    I am still fiddling with Storytlr a bit. I am looking at a better way to manage static content. The way I REALLY would like for this to happen is for storytlr to have some type of in-built wiki, but I am playing with the idea of just making static pages more manageable.

    All my code so far has been very nooby hacky stuff not worth sharing. When I do come up with some good code I will almost certainly get a github account

Leave A Comment

Your email will not be published.