Six Apart News & Events

Developing Movable Type Interface Plugins With BigPAPI

Professional Network member Kevin Shay wrote the following tutorial to introduce the community to his exciting new plugin and its development potential.

The BigPAPI plugin for Movable Type allows you to create plugins that can easily add features to Movable Type's interface. This article explains the background behind the develpment of BigPAPI, and walks through the creation of a simple working plugin.

Why BigPAPI?

With each new version, Movable Type has become friendlier to plugin development. The earliest plugins could do little more than add tags to the platform's templating system. Now (as of version 3.2), the Plugin API allows plugins to define not only template tags but text filters, conditional tags, global tag attributes, object callbacks, application callbacks, object actions, itemset actions, and junk filters. And a plugin can contain its own CGI scripts that are tightly integrated with Movable Type, and easy to develop, thanks to the constantly improving API.

There's always been one missing element, though. A plugin has never been able to incorporate itself directly into Movable Type's own interface. While plugins can have their own interfaces that coexist with the core system, or define certain types of actions that will show up on some of the system's screens, developers have never had the ability to do things like add a field to an editing screen, or reorder the items in a menu, without making users hack some of the Movable Type program files.

Since Movable Type 3.16, alternate application templates have been an option, but they're somewhat limited; you can use an alternate template to add static HTML elements to the interface, but a plugin doesn't really have a way to interact with those elements. Also, using alternate templates creates problems when a new version of the application is released with changes to the original templates.

I created BigPAPI as a way around these limitations. It's a fairly simple plugin that doesn't do anything to the Movable Type interface by itself. Once it's installed, though, other plugins can augment or alter any page of the application in any way they see fit—no hacking necessary. (BigPAPI expands the Plugin API, hence the name.)

The Approach

BigPAPI takes advantage of the fact that Movable Type's codebase is neatly organized into modules, and the fact that Perl allows you to override a subroutine within a module.

The plugin essentially inserts itself at a couple of points so that it can run some callbacks before the MT::App module displays any application page. This means that in order to modify an application template before it's displayed to the user, a plugin simply needs to define the appropriate callbacks.

There are two types of callbacks, template and param callbacks. These are described fully in the BigPAPI documentation, so we won't go into much detail here. Instead, let's dive into an example.

HTML::Template

Before working with BigPAPI, you should have some familiarity with the Perl module HTML::Template. This is the module MT uses to build the templates for its interface. (Please note that these templates have nothing to do with the templates you create and edit within Movable Type, which are used to publish content.)

The templates for the application interface are in the directory tmpl/cms within your Movable Type installation. You may want to browse through some of these files to get an idea of the type of template code you'll be working with when writing a BigPAPI plugin.

A BigPAPI Plugin From Start to Finish

To illustrate the process of creating a BigPAPI-based plugin, let's take a simple piece of information that might be handy to have displayed in the application interface. The Categories listing shows the title of each category in a particular weblog. Let's suppose we want this page to display each category's description as well.

Getting Started

First we need to determine which of the templates in the cms directory Movable Type is using to build this part of the interface. You can usually figure this out based on the names of the templates, and by perusing the code of a likely template to see if it contains the expected interface elements for the screen in question. If that doesn't work, try following these steps:

  1. In Movable Type, navigate to the screen you want to modify.
  2. Look for the __mode parameter in the URL. In this case, it's list_cat.
  3. Open lib/MT/App/CMS.pm.
  4. In the big $app->add_methods() statement toward the top of the module, locate the mode. In this case, we see that 'list_cat' maps to the list_categories() subroutine.
  5. Find the subroutine in the module.
  6. At the end of the subroutine, look for a build_page statement. In this case, we find:
    $app->build_page('edit_categories.tmpl', 
    \%param);

Thus, the template we want is edit_categories.tmpl.

This approach doesn't work in all cases. But by poking around in CMS.pm and the template files themselves, you should be able to figure out which template you're dealing with.

Now that we know which template to target, let's begin writing some code. First, we need to define our plugin with some basic code that should be familiar if you've written, or examined, other types of plugins:

require 
MT::Plugin;
require MT;

my $plugin = MT::Plugin->new({
	name 
=> "CatListDescriptions",
	description => 'Display descriptions 
on the Categories 
listing.'
});
MT->add_plugin($plugin);

This gives us a plugin object we can pass when adding callbacks, and also registers the plugin so it'll show up in Movable Type's listing of installed plugins.

Tweaking the Template

In order to modify the category listing template, we first define a BigPAPI template callback:

MT->add_callback('bigpapi::template::edit_categories', 
9, $plugin, \&_template);

This means that whenever the system is about to build the edit_categories.tmpl template, it will first call the subroutine _template() in our plugin, and pass it a reference to the text of the template. Now we need to figure out what to do to that text. The best way to do that is to open the template, tmpl/cms/edit_categories.tmpl.

We want to add each category's description after its label. After looking through the template code, it becomes clear that the actual category listing is generated within a loop called CATEGORY_LOOP, and this is what displays the linked label:

<span 
style="margin-left: <TMPL_VAR 
NAME=CATEGORY_PIXEL_DEPTH>px;"><a href="<TMPL_VAR 
NAME=SCRIPT_URL>?__mode=view&_type=category&blog_id=<TMPL_VAR 
NAME=BLOG_ID>&id=<TMPL_VAR 
NAME=CATEGORY_ID>"><TMPL_VAR 
NAME=CATEGORY_LABEL></a></span>

But the more text we search for with a regular expression, the more prone our plugin will be to break with a future version of the template. Let's narrow it down to just the label itself, plus the closing link tag (we don't want the description to be linked):

<TMPL_VAR 
NAME=CATEGORY_LABEL></a>

The important thing here is to confirm that this text doesn't appear anywhere else on the template; we need a string that's as short as possible, but also unique, so that the change is applied in the right place.

So here's the code we'll use in our callback subroutine:

my $old = qq{<TMPL_VAR 
NAME=CATEGORY_LABEL></a>};
$old = 
quotemeta($old);

It's always a good idea to use Perl's quotemeta() function on a piece of HTML you're going to use in a regular expression, in case it contains characters thathave a special meaning within a regex.

For the replacement text, we want to retain the original string, and add a new HTML::Template variable:

my $new = 
<<HTML;
<TMPL_VAR 
NAME=CATEGORY_LABEL></a>
<TMPL_VAR 
NAME=CATLISTDESCRIPTIONS_DESCRIPTION>
HTML
$$template =~ 
s/$old/$new/;

That'll do it for the template callback. Our subroutine doesn't need to return anything, because $template is a reference, so changes made to $$template will apply to the original variable in memory.

Setting Parameters

Now we need to populate the variable we added to the template. For that, we define a param callback:

MT->add_callback('bigpapi::param::edit_categories', 
9, $plugin, \&_param);

This means that before Movable Type builds the category listing page, it will call our _param() subroutine, passing it a reference to a hash containing all the HTML::Template parameters that the system itself has already assigned.

Looking at the template again, we see that each row of the CATEGORY_LOOP has an element called CATEGORY_ID. We can use that to load the MT::Category object for each category. Then it's just a matter of putting the description into an appropriately named element in the row:

require MT::Category;
for my $row 
(@{$param->{'category_loop'}}) {
	my $cat = 
MT::Category->load({ 'id' => $row->{'category_id'} });
 
	$row->{'catlistdescriptions_description'} = 
$cat->description;
}

Note that we've named the parameter with our plugin's name as a prefix, so it's unlikely to conflict with any of Movable Type's parameters, or anything any other plugin might want to place on the template.

We're Done

That's it! Here's the complete plugin:

use strict;
package 
MT::Plugin::CatListDescriptions;

require MT::Plugin;
require MT;

my 
$plugin = MT::Plugin->new({
	name => "CatListDescriptions",
 
	description => 'Display descriptions on the Categories 
listing.'
});
MT->add_plugin($plugin);

MT->add_callback('bigpapi::template::edit_categories', 
9, $plugin, 
\&_template);
MT->add_callback('bigpapi::param::edit_categories', 
9, $plugin, \&_param);

sub _template {
	my ($cb, $app, 
$template) = @_;
	my $old = qq{<TMPL_VAR 
NAME=CATEGORY_LABEL></a>};
	$old = quotemeta($old);
 
	my $new = <<HTML;
<TMPL_VAR 
NAME=CATEGORY_LABEL></a>

<TMPL_VAR 
NAME=CATLISTDESCRIPTIONS_DESCRIPTION>
HTML
	$$template =~ 
s/$old/$new/;
}

sub _param {
	my ($cb, $app, $param) = @_;
 
	require MT::Category;
	for my $row 
(@{$param->{'category_loop'}}) {
		my $cat = 
MT::Category->load({ 'id' => $row->{'category_id'} });
 
		$row->{'catlistdescriptions_description'} = 
$cat->description;
	}
}

1;

This brief piece of code is all it took to dynamically add a useful, if trivial, element to Movable Type's interface. And now any user can make the same addition simply by uploading CatListDescriptions.pl to their plugins directory (in addition to BigPAPI.pl). With Movable Type 3.2, you can easily add code so users can configure your interface changes not to appear, or to appear with different settings, on specified weblogs.

Scratching the Surface

The plugin we've just implemented is hardly earth-shattering, but hopefully it's given you an inkling of the possibilities. I've already released a few slightly more substantial BigPAPI plugins: LinkEntryToFile, which applies Movable Type's "Link this template to a file" functionality to the text of entries; MainMenuRecent, which displays the latest entries for each weblog on your Main Menu; UpdateAuthoredOn, which lets you set the Authored On timestamp of an entry by clicking a button; and WeblogsActionMenu, which lets you jump directly to somewhere other than the menu in a different weblog.

But there's much more to come, and the real potential for this type of plugin will be realized when it's used as one component of larger-scale plugin applications—a plugin might have its own CGI script interface, but also use BigPAPI to integrate itself into the main Mpvable Type interface.

I'll be updating the documentation with more plugins as they become available. If you come up with a plugin based on BigPAPI, please let me know, and I'll add it to the list. I look forward to seeing what the Movable Type developer community can do with this tool.

1 Comments
Pozycjonowanie said:
April 29, 2007 5:36 AM

Brilliant idea. btw. I really enjoyed reading all of your posts. It’s interesting to read ideas, and observations from someone else’s point of view… makes you think more. Greetings

Leave a Comment