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.
|
Related Reading
Essential Blogging |
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.
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.
|
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.
blog | A reference to the current MT::Blog instance in context. |
blog_id | An integer value of the current weblog's ID. The equivalent of MT::Blog->id stored for convenience. |
entries | A reference to an array of MT::Entry objects representing the entries being currently processed. |
entry | A reference to the current MT::Entry instance in context. |
category | A reference to the current MT::Placement instance in context. |
comment | A reference to the current MT::Comment instance in context. |
ping | A reference to the current MT::Ping (TrackBack ping) instance in context. |
tag | A 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.
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.
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 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.
|
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.
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);
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.
plugin | A scalar containing a unique name identifying the plugin. |
key | A scalar containing a unique key for identifying this record. |
data | A 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.
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.
Declare a package for your plugin. It creates your own
namespace and helps avoid collisions with other plugins a user may have
installed. I highly recommend using prefixing your packages with MT::Plugin:: for clarity.
Declare the version number of your plugin. Metadata is always good for future uses; it keeps you and everyone else sane. All it takes is two lines:
use vars qw( $VERSION );
$VERSION = 0.0; Avoid loading external (non-MT) modules at compile time.
Movable Type loads and compiles all plugins each time one of its CGIs
are evoked. In order to keep operations that do not need this
functionality from being penalized, it's recommended that you declare
modules for use in the subroutine that requires that module. Notice how
in my text-formatting plugin example, I declare the use of Text::Tiki in the tiki subroutine and not globally.
Declare all of your tags at the beginning of the code. Place tag functionality in external named subroutines. These go hand in hand and just makes your code more readable and easier to debug. Placing your tag functionality outside of the tag registration has the added benefit of code reusability. For instance, multiple tags can call the same subroutine and respond differently, based on the stashed tag name.
Use hierarchical naming and mixed caps. MT tag style uses a method similar to WikiWords,
where spaces are removed and each word is capitalized. When naming your
tags, think of them existing in a hierarchy where each word represents
a branch in its path. Note in our container tag example how I named
the tags SimpleLoop and SimpleLoopIndex not SimpleLoop and SimpleIndex. In doing so, it becomes clear that SimpleLoopIndex belongs to SimpleLoop. (See this document for more on the Movable Type template and tag philosophy.)
Stash variables with unique prefixes. In our container tags example, I stashed a value with a key of loop_index.
This isn't terribly unique, and it stands to reason that another plugin
developer may use that same key in his or her plugin. When I stash something
I always (except in examples) add the name of my plugin as a prefix to
the variable name. It helps identify what stashed that data and it also
makes it unlikely that another plugin would utilize that key and cause
a collision.
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.