Creating my first Habari plugin

In this post I detailed my reason for creating this plugin, and what problem it solves.

In a nutshell, I want to add buttons to the admin interface for a new Entry that will add a particular tag or set of tags and publish the post when clicked.

Where to start?

I figured that the Creating A Plugin page on the Habari wiki would be a good choice. It shows you what files you need to create in order to have your plugin recognized, so I won't repeat that here. I also learned that plugins are based on Actions and Filters. An Action occurs when an event in the software takes place. A Filter is similar, but expects you to change the data passed in and return it. Both Actions and Filters have a "hook name" to represent the event, and you can capture these by creating a "sink" function in your plugin, like so:

function action_plugins_loaded()


function filter_spam_filter($rating, $comment, $handler_vars)
        if ( strpos( $comment->content, 'viagra' ) ) {
        return $rating;

But how to find these hooks?

The snag, at least for a beginner, comes in trying to find what hooks you need to create sinks for. I can think of 4 ways to figure this out:

  • Find a plugin that is operating in a similar context. e.g., if you want to add options to a section of the admin interface, find a plugin that adds something to the same location. Download the plugin and you can view the code that makes it work. Learning from other people's code is an invaluable way to gain insight into a new system.
  • Look at the: list of available hooks on the wiki. This can be daunting because if you're like me the filenames don't mean anything yet and there are so many, but some are obvious. Like 'user_insert_before', or 'user_update_before'
  • Poke through the code yourself. This is what I did, mainly. You can learn a lot by searching through the code using grep (or even better, ack). If you can find where the actual data is assigned to objects or written to the database you'll find lots of calls to Plugins::act() sprinkled around.
  • Ask someone on IRC (#habari on or the Habari mailing list. Remember it's good to have done some of your own research in advance.

The first action I found was 'form_publish' for adding the buttons to the form. I poked around in the FormUI documentation as well as the code around where the action is called and found the syntax for adding buttons to an existing part of the form and created my first sink.

My (slightly simplified) action sink:

public function action_form_publish ( $form, $post, $context )
        $buttons = $this->get_configuration();
        foreach($buttons as $btn_name => $tags) {
            $id = $this->_make_id($btn_name);
            $form->append('submit', $id, $btn_name, 'admincontrol_submit');

I was amazed how simple this was, and the results were very good.

I tried using the FormUI method move_after() so I could move the buttons to the right of the existing Publish button, but I kept getting strange errors. More on that later... Next I looked for a way to intercept the post prior to publishing and add the appropriate tags.

My initial thought was that in the Action 'post_publish_before' I could add the tags, and I began looking for a way to save and publish rather than just Save so that I could make my buttons act like the Publish button. However, in the course of trying to find the Publish button I found out that it is actually generated by Javascript! It is dynamically added to the form when the admin page is shown, and an onclick event is added to it that submits the form. I realized that I had been thinking about publishing incorrectly. I was assuming it was stored differently from a normal saved post, but it appears that the post status is just set to indicate that it is published.

Armed with this knowledge, I found what I thought was the correct Action 'post_insert_before' so that I could add my tags to the post and mark it as published before it is saved. However, I got hung up on major detail - I didn't know how to figure out which submit button was pressed. 'post_insert_before' only had a Post object, which had already been given the parameters that the admin handler deemed important. Back to the Action hunt. I paged through the AdminHandler class and found an Action that looked promising: 'publish_post'. It had both a Post object and a FormUI object, and the post gets updated right after the action sink is called. After much trial and error I managed to get the status and tags saving correctly.

Unfortunately it still wasn't working properly. It turns out that the FormUI object passed to the Action is the original form in its entirety, and has no knowledge of what was actually submitted. So even though I could save tags properly, I still didn't know which button had been clicked to submit the form. Back to AdminHandler. I found a page-specific Action that fires when a $_POST array is present in the admin interface, which looked promising. Had I managed to spell my sink function name correctly, I would have been done much sooner...

Eventually, however, I figured out my error and with a few more Utils::debug() calls and a little more browsing the source code for examples I was able to figure out how to get the value of the submitted button and I used that to store the tags that I needed to add to the post. This let me switch my other Action back to 'post_insert_before' as I no longer needed the FormUI parameter, and that Action is closer to where I want the post updated. And it works! I could still mess with formatting the buttons better, and providing a configuration for the plugin rather than having the buttons hard-coded, but I'll save that for Part 2 (and if anyone actually wants this plugin more widely available).

        See what button was clicked and save the tags
    public function action_admin_theme_post_publish ( $admin_handler, 
                                              $theme )
        // Seems like there has to be a better way to do this
        $buttons = $this->get_configuration();
        foreach($admin_handler->handler_vars as $name=>$value) {
            if( isset($buttons[$value]) ) {
                $this->submitted_tags = $buttons[$value];

        Add the tags to the post
    public function action_post_insert_before ( $post )
        // check if we had any submitted tags
        if(count($this->submitted_tags)) {
            // set post as published
            $statuses = Post::list_post_statuses( false );
            $post->status = $statuses['published'];

            $current_tags = $post->tags;
            $updated_tags = array_merge(array_values($current_tags), 

            $post->tags = array_unique($updated_tags);

Update: Habari .7 replaces tags with taxonomies, so I had to change how the tags work. This is for the better, as the above method felt like I was taking advantage of the array internals of the tags object rather than understanding how things work. The following works in the later versions of Habari, although the tag needs to exist first. Replace the tags bit of the above code with this:

foreach($this->submitted_tags as $tag) {
    $tag_obj = Tags::get_one($tag);
Simulating two Habari blogs with one install (on the same domain)

Now that I have Habari installed, I have been thinking about how I want to lay out my site. I decided that I want to maintain a development blog, but that I would probably also want to have a personal/family blog at some point, and that those should remain separate. My family doesn’t care about my opinions on the latest version of Python, and those who might be interested in my thoughts re: Python don’t want to hear about what my (hypothetical) kids did on our last family vacation.

However, I would like to use the same admin interface and the same Habari install, so I don’t have to maintain the same sets of plugins and custom themes in multiple places. Picky, I know. I don’t see anywhere that indicates Habari would support this specific use case. (Even keeping the admin interfaces separate doesn’t seem to be supported, as note #5 from the multisite page on the wiki states: “Sub-directory sites do not correctly function at the moment. Independent domains and subdomains function very well.”)

After some discussion with the helpful folks on IRC (#habari on and a lot of poking around on the Habari wiki I came up with a plan. Caveat: I don’t actually know very much about how Habari works yet, so it may be that there are parts of this plan that aren’t possible or need tweaking. But it sounds plausible.

A comment from IRC pointed me towards Habari Asides, which I had seen referenced in a couple themes, and suggested that I could use Asides to post different kinds of posts. However, when I looked at the wiki page, I saw that there is nothing technically different about Asides than normal posts - code that handles them just looks for a particular tag and includes or excludes them as needed.

Example from the wiki to exclude a particular tag:

public function act_display(
            $paramarray= array( 'user_filters'=> array() ) )
        $paramarray['user_filters']['not:tag']= 'aside';
        parent::act_display( $paramarray  );

Example from the wiki to extract just posts with a particular tag:

$this->assign( 'asides', Posts::get( array( 'tag'=>'aside', 
                                                     'limit'=>5) ) );

For Habari .7 and later, you need to use the new taxonomy syntax, like this:

$this->assign( 'asides', Posts::get( array( 
             'vocabulary'=>array('tags:term' => 'life'), 
             'limit'=>5) ) );

This is great, and super simple. I should be able to create two pages on my site, one which acts like a home page for a personal blog and one that acts like a development blog home page, simply by excluding tags that I don’t want to see (or only showing posts with the particular tag I want to see). I just have to make sure that every post is tagged for either one blog or the other one.

I also think that I could make the tagging requirement a little more invisible, so that I don’t have to remember to tag each post every time. I could create a plugin that provides me with additional Publish buttons, ‘Publish to Life’ and ‘Publish to Dev’ (or something). When I click these, it would automatically assign the appropriate tag to the post and publish it.

- Create the custom theme that makes my home page mostly static and two different pages that each look like a normal blog landing page.
- Exclude the appropriate tags from each blog page.
- Create a plugin that gives me a Publish button for each blog.

I think this should work. I’ll tackle the plugin first, as I think it will be easier and will get me started messing around with 'pluggables' in Habari.

UPDATE: The plugin works now, and here's how it came about.

Tagged: and
Thank goodness for backups

A brief note now that Habari is working. I was attempting to set Habari up to use an existing SQLite database that already contained some tables related to other pieces of my site. I attempted to use the config file setup from the wiki, and that resulted in an error in the installer. I don't know which key was causing problems, but when I checked my files, my database was gone. Habari had deleted my database file.

Thankfully I had just created a backup, so I was able to restore the file. I then tried an install again, this time letting Habari do all of the work setting up the config file. I entered the path to the database through the web interface this time and the install worked. I then went and sure enough the database was where I expected it to be. However... I then looked at the database and all of the tables had been dropped except for the habari_ tables! What is the point of having table prefixes if the installer is just going to drop all of the existing tables in the database? Time to back up again - looks like having other information in the Habari database is going to cause problems.

So, long story short:

Don't point the Habari installer to an existing SQLite database. Habari will replace it.

Tagged: and

This site is running Habari, a state-of-the-art publishing platform! Habari is a community-driven project created and supported by people from all over the world. Please visit to find out more!

Tagged: and