
Developing Movable Type Plug-ins
Pages: 1, 2, 3
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.
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.
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.
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.
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.


|