Web DevCenter
oreilly.comSafari Books Online.Conferences.
MySQL Conference and Expo April 14-17, 2008, Santa Clara, CA

Sponsored Developer Resources

Web Columns
Adobe GoLive
Essential JavaScript
Megnut

Web Topics
All Articles
Browsers
ColdFusion
CSS
Database
Flash
Graphics
HTML/XHTML/DHTML
Scripting Languages
Tools
Weblogs

Atom 1.0 Feed RSS 1.0 Feed RSS 2.0 Feed

Learning Lab






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.

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.

  • 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.