Web DevCenter    
 Published on Web DevCenter (http://www.oreillynet.com/javascript/)
 See this if you're having trouble printing code examples


Developing Movable Type Plug-ins

by Timothy Appnel
03/19/2003

One of the most significant things to come out of the weblog movement is the availability of low-cost and relatively easy-to-use tools for publishing. One of the most sophisticated and powerful is Movable Type, developed by Ben and Mena Trott of Six Apart. Movable Type's features are so rich that the tool's uses have begun to transcend weblogging.

Written in a highly modular Perl object-oriented (OO) style, Movable Type (MT) has an open code base (it's not open source—an important distinction) that makes the browser-based tool quite flexible and easily modifiable to adapt to any number of publishing applications. In recent releases, extending MT has become easier and more elegant with the introduction of a plugin framework that continues to be enhanced with each new release.

In this article I will cover the MT plugin framework, its complete API, and the basics of hooking into the core systems operation and its data persistence service. It's assumed that you're somewhat knowledgeable with Perl and familiar with its OO style. (See Simon Cozens' article on Object-Oriented Perl for a quick primer.) With the richness of Perl and the MT system, a whole book could be written on the subject. We'll just cover the basics in this whirlwind tour.

The Movable Type Plugin Framework

Related Reading

Essential Blogging
Selecting and Using Weblog Tools
By Cory Doctorow, Rael Dornfest, J. Scott Johnson, Shelley Powers, Benjamin Trott, Mena G. Trott

As I've mentioned, Movable Type provides developers with a framework to easily and elegantly extend the base functionality of the system using OO Perl. Generally speaking, plugins are limited to MT's template processing engine, to add variable, container, and conditional tags in addition to global filters. In version 2.6, the plugin framework began to branch out from template processing by introducing an API for hooking in text-formatting engines and access to MT's data persistence mechanisms. We'll go into the specifics of what these can do for you throughout the article.

Installing plugins are quite easy. Simply place the code in a subdirectory named plugins in the main Movable Type directory where mt.cgi resides. (If you are running an older version of MT and have not installed a plugin before, you may have to create this directory.) MT will attempt to load any file in the plugins directory ending with .pl as a plugin to the system. Plugins are available to all weblogs hosted by this instance of MT.

In addition to the plugin framework, MT provides an extensive and well-documented array of functionality through its OO Perl modules, giving developers the ability to create command-line utilities to their own extension applications. We won't be able to cover this topic in this article, but the documentation is available here.

Let's dive into our first plugin.

MTHelloWorld: Your First Movable Type Plugin

Let's start with a basic variable tag that simply inserts a value into a template when in encountered.

package MT::Plugin::HelloWorld;
use MT::Template::Context;
MT::Template::Context->add_tag(HelloWorld => sub { return 'Hello World.'; } );
1;

Save this code to a file named mt-helloworld.pl in the plugins directory as we discussed, and presto! — <MTHelloWorld /> in your templates will be replaced with the string Hello World. Let's review what our first plugin is doing.

In the first line I declared a package of MT::Plugin::HelloWorld. This declaration is optional, but is good form to avoid namespace collisions with other plugins or MT. Using MT::Plugin:: is another good practice for clarity and similar reasons.

In the second line we call into service MT::Template::Context, the module that contains the majority of the plugin magic and is the main workhorse during content generation.

Finally we register our HelloWorld tag by passing in a single key-value hash. The key is the tag name (case matters) you are registering. The MT prefix is assumed and will be appended by the system during processing. The value is a subroutine that will be called when the tag registered is incurred during processing. Here I placed the subroutine directly in the hash because it was so simple, but it's generally good form to use the anonymous subroutine to call an external named subroutine. This practice makes your code easier to read and affords you the advanced practice of reusing the subroutine with multiple tags.

Let's add a bit more sophistication to this plugin. Suppose we want to be able to say "Hello" to a specific world, and define that world in our template markup. Here is what our code may look like:

package MT::Plugin::HelloWorld;
use MT::Template::Context;
MT::Template::Context->add_tag(HelloWorld => \&hello_world); 
 
sub hello_world {
  my $ctx = shift;
  my $args = shift;
  return "Hello " defined($args->{world})?$args->{world}:'World';
}
1;

Now we can use <MTHelloWorld world="Krypton"/> and Hello Krypton will be inserted in our template. If we don't declare a world argument, we still get Hello World. Let's break down what's different.

Since our tag's routine is getting a bit more sophisticated, in the third line we've moved it to a named subroutine that's external to this command. Moving to that subroutine, we see in the first two lines that MT passes template tag plugins two references for use in our plugin routine. I've coded them longhand for clarity, but you can use whatever brand of Perl kung fu you practice to work with these references.

The first reference declared as $ctx is the current instance of MT::Template::Context. This object instance that carries all of the information to the template processor's current state is essential to almost any type of plugin processing. We'll delve into some of the common contents of this object instance in a later section.

The second reference declared as $args is a hash of any arguments that were defined in the template. In the third and final line of the subroutine, we use this hash to check if an argument of world has been defined and create our return string accordingly, using World as a default. In good Perl form, all other arguments are ignored. (Perhaps they're for the global filter handler.)

NOTE: Our particular example would always insert something into a template. However, in some instances you may decide to insert nothing, based on some condition. In these cases you must return a null string and not an undefined value. An undefined value returned from a plugin is treated as an error and will stop all processing.

Variable replacement isn't really that interesting. The real power of plugins is when they hook into the system and are used in combination with other tags. Before continuing on to the rest of the plugin framework, we'll review some of the common ways we can hook into MT's processing.

The Stash

Hooking into MT's template processing starts with the stash method provided by the MT::Template::Context module. Through stash, we can retrieve the current information MT is processing templates with at that moment. We can also use stash to store our own information for later use by other associated tags. Here is a quick example of its use:

# This line stores $value in the current context with a key of 'foo.'
$ctx->stash('foo',$value);

# This line retrieves the value of foo and assigns it to $value
my $value = $ctx->stash('foo');

As MT works its way recursively through the tags it encounters while processing templates, it is constantly adding, retrieving, and clearing values from the stash. Here are some of the current keys MT will use during template processing.

blogA reference to the current MT::Blog instance in context.
blog_idAn integer value of the current weblog's ID. The equivalent of MT::Blog->id stored for convenience.
entriesA reference to an array of MT::Entry objects representing the entries being currently processed.
entryA reference to the current MT::Entry instance in context.
categoryA reference to the current MT::Placement instance in context.
commentA reference to the current MT::Comment instance in context.
pingA reference to the current MT::Ping (TrackBack ping) instance in context.
tagA string representing the current tag name being processed. The MT prefix is omitted.

Other than tag, these stashed references provide access to content that has been retrieved by MT from the database into memory, based on the template processing's current context that has been determined by the template type or by another tag.

As we'll see in the examples that follow, this information in the stash is quite handy to developing our own plugins. Now let's return to the rest of the MT plugin framework.

Container Tags

As we've discussed, variable tags alone are not terribly interesting. Another construct supported by MT is the container tag. As its name implies, this type of tag contains additional markup and template tags within start and end tags. Container tags allow us to process a block of template code and/or create a context from which other tags can draw their data. Here is a simple example from MT's built-in tags of a container (MTEntries) that creates a list of weblog entry titles inserted into the template by MTEntryTitle.

<MTEntries>
   <MTEntryTitle /><br />
</MTEntries>

Programming container tags requires a bit more consideration, because it is likely that the contents of the container tag require further processing. Let's review an example of a simple container with an associated variable tag.

package MT::Plugin::SimpleLoop;
use MT::Template::Context;
MT::Template::Context->add_container_tag(SimpleLoop => \&loop );
MT::Template::Context->add_tag(SimpleLoopIndex => \&loop_index );
 
sub loop {
  my $ctx = shift;
  my $args = shift;
  my $content = '';
  my $builder = $ctx->stash('builder');
  my $tokens = $ctx->stash('tokens');
  for my $i(1..$args->{loops}) {
    $ctx->stash('loop_index',$i);
    my $out = $builder->build($ctx, $tokens);
    $content .= $out;
  }
}
 
sub loop_index {
  my $ctx = shift;	
  return $ctx->stash('loop_index');
}

1;

With this plugin implemented, we can create a list of integers in our template like this:

<MTSimpleLoop loops="10">
  <MTSimpleLoopIndex/><br />
</MTSimpleLoop>

Looking back at the example code, things start off like our variable tag replacements. I declare a package and the use of MT::Template::Context class before registering one container tag and one variable tag with their associated subroutines. Moving on to the first subroutine of loop, we begin as before by assigning the Context class and tag argument hash references to variables.

As mentioned, container tags differ from their variable counterparts in that other template tags are assumed to be within them. This means that at some point, we have to pass the container tags' contents back into the template-processing engine before ending our subroutine. Here in our example, we retrieve a reference to the template builder class (MT::Builder) that has been stashed by the system and store it in $builder. We also get a reference to the collection of processing tokens it has created from the stash and store it in $tokens.

With everything in place, we start loop. First we stash the current index of the loop in loop_index. Next we pass the current context and the processing tokens back to the builder, eventually storing the result of that processing in $out. We concatenate this result to previous results in $content and loop again.

To see how we use the loop_index value we stashed, we go to the second subroutine, where we retrieve that value and return it as a string.

This is an excellent example of how tags work together using stash, and begins to demonstrate the possibilities of extending MT's operation with plugins. Let's press on.

Conditional Tags

We'll only take a cursory look at the conditional tag, since it's just a specialized container tag that has been added to the API for convenience.

Subroutines registered as conditional tags need only return a true or false value. MT automatically handles whether the conditional tag's contents should be passed on for further processing or be stripped from the template's output. In other words, there is a need to wrap the builder object in a conditional. Here is a simple implementation of two conditional tags.

package MT::Plugin::ConditionalExample
use MT::Template::Context;
MT::Template::Context->add_conditional_tag(IncludeThis => sub { return 1 });
MT::Template::Context->add_conditional_tag(Excludethis => sub { return 0 });

With this plugin implemented, we could use the following markup into our template:

<MTIncludeThis>This text will appear.</MTIncludeThis>
<MTExcludeThis>This text will be stripped.</MTExcludeThis>

Only the phrase: "This text will appear." would remain, since that conditional tag's subroutine returns a true value to MT's template builder.

Global Filters

Global Filters are not tags, but arguments that can be added to any Movable Type template tag. Global filter arguments take a single value (quite often just a "1" to signify "on") and invoke a filter that is applied to the tag's content right before insertion. MT's native global filters include routines for stripping markup tags, encoding XML, and converting text to lower case.

Global filters can be a sophisticated as you like, but generally they tend to be quite simple. Here is an example of a global filter that will strip out blank lines.

package MT::Plugin::StripBlanks
use MT::Template::Context;
MT::Template::Context->add_global_filter(strip_blanks => sub { &strip_blanks });
sub strip_blanks { 
  my $text = shift;
  my $arg_value = shift;
  my $ctx = shift;
  $text=~s/^\s*?\n//gm if ($arg_value); 
  return $text
}
1;

Once again, I've written this example out "longhand" for clarity. Note that the values passed to a global filter routine are different than those in its tag-based counterparts. Global filter routines are passed a scalar with the text to be processed, a scalar of the value of the argument, and finally, a reference to the Context class instance.

In our example, the argument value stored in $arg_value and the context object stored in $ctx are not of any use, so we just ignore them. We apply a simple regular expression to the value of $text and return it.

With this plugin implemented we can do …

<MTEntries strip_blanks="1">
  ...
</MTEntries>

… and all blank lines will be stripped out of the result's template markup within the MTEntries tagset.

Text-Formatting Plugins

With the release of Movable Type 2.6, the plugin framework has begun to branch out from template processing by introducing an API for hooking text-formatting engines into the system. The intention of this type of plugin is to provide more control and an easier means of authoring content in MT's browser-based interface for non-technical users who are not XHTML savvy.

Text-formatting engines handle the formatting of structured text notation into some other markup language, such as XHTML. In some ways, text-formatting plugins are like global filters; however, there are some noteworthy differences. Global filters must be explicitly declared in each template, forcing all authors to use the same formatting style, in addition to rendering MT's preview function useless. In this example, we'll look at a subset of an early text formatting plugin I developed using a text notation called TikiText.

package MT::Plugins::TikiText;
use MT;
MT->add_text_filter('tiki' => {
  label => 'TikiText',
  docs => 'http://www.mplode.com/tima/projects/tiki/',
  on_format => \&tiki
});
 
sub tiki {
  my $text=shift;
  my $ctx=shift;
  require Text::Tiki;
  my $processor=new Text::Tiki;
  return $processor->format($text);
}

There are a number of significant differences in how you implement this type of plugin. The first is that text-formatting plugins are registered using the MT module and not the Context module. Another significant difference is that registering text-formatting plugins is a bit more involved.

A text-formatting engine is registered with a single key and an associated options hash. In our example we use the key tiki. The key used is quite significant because it will be stored with each entry to determine which formatting engine to apply when published. This key should be lowercase and only contain alphanumeric characters and the "_" (underscore) character. This key should not change once deployed.

Moving on to the options hash associated to the tiki key, we set label with a short descriptive name of TikiText that will be used in the MT interface. Next we define the URL of any documentation to the format with docs. (MT creates a link in its interface to this documentation for easy user access.) Like its predecessors, I define the subroutine that will handle the text formatting using on_format.

As the tiki subroutine demonstrates, text-formatting plugins are passed the text to be processed and may optionally receive a context object if it is invoked while processing templates. I created the TikiText processor as a separate Perl module for the sake of reusability, so we simply declare its use, instantiate it, and return the formatted text.

Error Handling

As developers, we know that things don't always go as planned, and we have to be prepared to handle errors. In keeping my examples simple and easily digestible, I glossed over error handling. Let's address that now.

I've already mentioned that returning an undefined value from a plugin routine will be interrupted as an error by MT and stop processing. While these error messages are better then a completely uninformative 500 error, we can do better to inform a user of what error has occurred and how they may correct it.

Movable Type's Context class inherits an error method from MT::ErrorHandler for returning an error condition and message back to the system and the user.

return $ctx->error('An informative error message to help the user.');

The Context class also inherits an errstr method, which retrieves the last error message set.

warn $ctx->errstr;

These methods have many uses, but here are some of the most common:

# Checking if a tag is being called in a particular context. In this case 
# we are checking if our tag has been placed inside of an entry context.
$ctx->error('MT'.$ctx->stash('tag').' has been 
         called outside of an MTEntry context.') 
  unless defined($ctx->stash('entry'));
 
# Checking if a required argument (name) has been passed. 
$ctx->error('name is a required argument of '.$ctx->stash('tag').'.')
  unless defined($args->{'name'});
 
# Catch any errors during a template build and pass it to the context.
defined(my $out = $builder->build($ctx,$tokens))
    or return $ctx->error($builder->errstr); 

Plugin Data Storage

Also new to the version 2.6 framework is the addition of the MT::PluginData class, which provides plugin developers direct and convenient access to MT's data persistence mechanism. Like MT::Entry and similar MT native objects, MT::PluginData inherits the MT::Objects object. This abstraction saves MT's code from having to deal with the difference in the underlying data storage mechanism that MT is using. (MT now supports Berkeley DB, MySQL, SQLite, and PostgreSQL.)

Unique to this module are the plugin, key, and data methods that are provided in addition to the underlying MT::Object functionality that is inherited.

pluginA scalar containing a unique name identifying the plugin.
keyA scalar containing a unique key for identifying this record.
dataA reference to a data structure to be stored in the database. MT uses the standard Perl module of Storable to serialize the data structure first.

Here is the example from the plugin data module documentation with my comments.

use MT::PluginData;
my $data = MT::PluginData->new;
$data->plugin('my-plugin'); 
$data->key('unique-key');
$data->data($big_data_structure);
# Remember $big_data_structure has to be a reference.
# $data->data('string'); # ERROR!
# $data->data(\'string'); # CORRECT!
 
$data->save or die $data->errstr; # save is inherited from MT::Object.
 
# Elsewhere retrieving this data would look something like...
my $data = MT::PluginData->load({plugin => 'my-plugin', 
  key => 'unique-key'});
my $big_data_structure = $data->data;

We're only scratching the surface here. Covering MT's data persistence mechanism could be an article in and of itself. What's important is that you know it's there and for you to take advantage of it.

Best Practices

Here's a quick summary of best practices that I have learned through developing several Movable Type plugins of my own and reviewing the code of dozens of others by my MT colleagues.

Conclusion

As I hope you can tell by the length of our whirlwind tour, the richness of Perl and the MT Plugin framework provides developers with a great deal of power and flexibility in extending the system. With a bit of OO Perl know-how, the potential use of Movable Type for any number of publishing applications not only becomes possible, but fairly easy to implement.

For more details and latest information check these resources:

Timothy Appnel has 13 years of corporate IT and Internet systems development experience and is the Principal of Appnel Internet Solutions, a technology consultancy specializing in Movable Type and TypePad systems.


Return to the Web Development DevCenter.

Copyright © 2009 O'Reilly Media, Inc.