How to Write a Plugin


Writing a plugin

Overview

A plugin is a piece of software that extends the Movable Type publishing platform in one of a number of ways. A plugin can create new Movable Type template tags, they can add attributes to existing tags (these are called ``global filters''), they can process entry text when it is being built, and they can install callbacks to process objects whenever database events occur. Plugins

MT::Plugin class

New in MT3, the MT::Plugin class encapsulates all the data about a plugin that a user would interact with. This allows users to manage their installed plugins within the graphical UI.

It is recommended that every plugin register an MT::Plugin object so that users always know what plugins are installed. However, if a plugin does not register itself this way, it is still expected that other plugin functions will work.

In particular, MT::Plugin holds the name & version of the plugin, as well as a short description, and links to documentation and a configuration interface. To supply this information, there are at least two styles that plugins can employ.

There's More Than One Way To Do It

A plugin may choose to write a subclass of MT::Plugin which implements the methods name(), description(), doc_link() and config_link(), returning the appropriate piece of data in each case.

Alternatively, a plugin may simply use an instance of MT::Plugin, and set the appropriate fields using its methods:

$plugin = new MT::Plugin();
$plugin->name("Arthur Dent's Duplicator Plugin, v.0.45");
$plugin->description("Duplicates weblog posts with a minimum of fuss.");

Adding a Plugin to the Interface

Once an MT::Plugin object has been created, the plugin can establish its foothold in the MT interface by calling the MT method add_plugin_slug($plugin). This will create a slot on the Main Menu where the plugin's name and description are listed, and containing links to the configuration interface and the documentation pages.

Your plugin file should have an extension of pl (files with other extensions will be ignored when searching for plugins). Your configuration page will normally have an extension of cgi, because it needs to be executed as a CGI program by the webserver. But keep in mind that some webservers will want CGI programs to be renamed with a pl extension. Therefore, it's a good idea to keep distinct the basenames (without extension) of your files.

Adding tags

Movable Type comes shipped with a wide variety of template tags that authors and designers can use in developing a customized look and feel for their site.

Plugin authors can extend this system by adding new template tags, which can produce any string that can be computed. Tags are added using the add_tag and add_conditional_tag methods of MT::Template::Context.

MT::Template::Context->add_tag($name, \&subroutine)

add_tag registers a simple ``variable tag'' with the system. An example of such a tag might be <$MTEntryTitle$>.

$name is the name of the tag, without the MT prefix, and \&subroutine a reference to a subroutine (either anonymous or named). \&subroutine should return either an error (see ERROR HANDLING) or a defined scalar value (returning undef will be treated as an error, so instead of returning undef, always return the empty string instead).

For example:

MT::Template::Context->add_tag(ServerUptime => sub { `uptime` });

This tag would be used in a template as <$MTServerUptime$>.

The subroutine reference will be passed two arguments: the MT::Template::Context object with which the template is being built, and a reference to a hash containing the arguments passed in through the template tag. For example, if a tag <$MTFooBar$> were called like

<$MTFooBar baz="1" quux="2"$>

the second argument to the subroutine registered with this tag would be

{
    'quux' => 2,
    'bar' => 1
};

add_container_tag

MT::Template::Context->add_container_tag($name, \&subroutine)

Registers a ``container tag'' with the template system. Container tags are generally used to represent either a loop or a conditional. In practice, you should probably use add_container_tag just for loops--use add_conditional_tag for a conditional, because it will take care of much of the backend work for you (most conditional tag handlers have a similar structure).

$name is the name of the tag, without the MT prefix, and \&subroutine a reference to a subroutine (either anonymous or named). \&subroutine should return either an error (see ERROR HANDLING) or a defined scalar value (returning undef will be treated as an error, so instead of returning undef, always return the empty string instead).

The subroutine reference will be passed two arguments: the MT::Template::Context object with which the template is being built, and a reference to a hash containing the arguments passed in through the template tag.

Since a container tag generally represents a loop, inside of your subroutine you will need to use a loop construct to loop over some list of items, and build the template tags used inside of the container for each of those items. These inner template tags have already been compiled into a list of tokens. You need only use the MT::Builder object to build this list of tokens into a scalar string, then add the string to your output value. The list of tokens is in $ctx->stash('tokens'), and the MT::Builder object is in $ctx->stash('builder').

For example, if a tag <MTLoop> were used like this:

<MTLoop>
The value of I is: <$MTLoopIValue$>
</MTLoop>

a sample implementation of this set of tags might look like this:

MT::Template::Context->add_container_tag(Loop => sub {
    my $ctx = shift;
    my $res = '';
    my $builder = $ctx->stash('builder');
    my $tokens = $ctx->stash('tokens');
    for my $i (1..5) {
        $ctx->stash('i_value', $i);
        defined(my $out = $builder->build($ctx, $tokens))
            or return $ctx->error($builder->errstr);
        $res .= $out;
    }
    $res;
});
MT::Template::Context->add_tag(LoopIValue => sub {
    my $ctx = shift;
    $ctx->stash('i_value');
});

<$MTLoopIValue$> is a simple variable tag. <MTLoop> is registered as a container tag, and it loops over the numbers 1 through 5, building the list of tokens between <MTLoop> and </MTLoop> for each number. It checks for an error return value from the $builder->build invocation each time through.

Use of the tags above would produce:

The value of I is: 1
The value of I is: 2
The value of I is: 3
The value of I is: 4
The value of I is: 5

add_conditional_tag

MT::Template::Context->add_conditional_tag($name, $condition)

Registers a conditional tag with the template system.

Conditional tags are technically just container tags, but in order to make it very easy to write conditional tags, you can use the add_conditional_tag method. $name is the name of the tag, without the MT prefix, and $condition is a reference to a subroutine which should return true if the condition is true, and false otherwise. If the condition is true, the block of tags and markup inside of the conditional tag will be executed and displayed; otherwise, it will be ignored.

For example, the following code registers two conditional tags:

MT::Template::Context->add_conditional_tag(IfYes => sub { 1 });
MT::Template::Context->add_conditional_tag(IfNo => sub { 0 });

<MTIfYes> will always display its contents, because it always returns 1; <MTIfNo> will never display is contents, because it always returns 0. So if these tags were to be used like this:

<MTIfYes>Yes, this appears.</MTIfYes>
<MTIfNo>No, this doesn't appear.</MTIfNo>

Only ``Yes, this appears.'' would be displayed.

A more interesting example is to add a tag <MTEntryIfTitle>, to be used in entry context, and which will display its contents if the entry has a title.

MT::Template::Context->add_conditional_tag(EntryIfTitle => sub {
            my $e = $_[0]->stash('entry') or return;
            defined($e->title) && $e->title ne '';
        });

To be used like this:

<MTEntries>
  <MTEntryIfTitle>
    This entry has a title: <$MTEntryTitle$>
  </MTEntryIfTitle>
</MTEntries>

Post-Processing Tag Output

In addition to adding new tags, a plugin can add an attribute which the template author can apply to any tag. The code associated with the attribute will be called to transform the output of the tag.

MT::Template::Context->add_global_filter($name, \&subroutine)

The $name is the name of the tag, for example, ``encode_html''. Any MT template tag that contains an attribute encode_html=value will trigger the given subroutine.

The code reference \&subroutine will be called as follows:

$string = &subroutine($string, $attribute_value, $context)

The $string parameter is the text to be transformed. The $attribute_value is the value given to the attribute in this invocation, for example:

<MTEntryTitle encode_html=1>

The &subroutine would be invoked with $attribute_value set to 1. The final argument to the subroutine, $context, is a reference to the MT::Template::Context object, which contains information about the context in which the tag was used.

Plugin Callbacks

Most MT::Object operations can trigger callbacks to plugin code. Some notable uses of this feature are: to be notified when a database record is modified, or to pre- or post-process the data being flowing to the database.

To add a callback, invoke the add_callback method of the MT::Object subclass, as follows:

MT::Foo->add_callback("pre_save", <priority>, 
                      <plugin object>, \&callback_function);

The first argument is the name of the hook point. Any MT::Object subclass has a pre_ and a post_ hook point for each of the following operations:

load
save
remove
remove_all
(load_iter operations will call the load callbacks)

The second argument, <priority>, is the relative order in which the callback should be called. Normally, the value should be between 1 and 10, inclusive. Callbacks with priority 1 will be called before those with priority 2, 2 before 3, and so on.

Plugins which know they need to run first or last can use the priority values 0 and 11. A callback with priority 0 will run before all others, and if two callbacks try to use that value, an error will result. Likewise priority 11 is exclusive, and runs last.

How to remember which callback priorities are special? As you know, most guitar amps have a volume knob that goes from 1 to 10. But, like that of certain rock stars, our amp goes up to 11. A callback with priority 11 is the ``loudest'' or most powerful callback, as it will be called just before the object is saved to the database (in the case of a pre-op callback), or just before the object is returned (in the case of a post-op callback). A callback with priority 0 is the ``quietest'' callback, as following callbacks can completely overwhelm it. This may be a good choice for your plugin, as you may want your plugin to work well with other plugins. Determining the correct priority is a matter of thinking about your plugin in relation to others, and adjusting the priority based on experience so that users get the best use out of the plugin.

The <plugin object> is an object of type MT::Plugin which gives some information about the plugin. This is used to include the plugin's name in any error messages.

<callback function> is a code referense for a subroutine that will be called. The arguments to this function vary by operation (see MT::Callback for details), but in each case the first parameter is the MT::Callback object itself:

sub my_callback {
    my ($cb, ...) = @_;
      if ( <error condition> ) {
        return $cb->error("Error message");
    }
}

Strictly speaking, the return value of a callback is ignored. Calling the error() method of the MT::Callback object ($cb in this case) propagates the error message up to the Movable Type activity log.

Another way to handle errors is to call die. If a callback dies, MT will warn the error to the activity log, but will continue processing the MT::Object operation: so other callbacks will still run, and the database operation should still occur.

Callback Priorities

Each time you register a callback, you supply a 'priority' which controls the order in which plugins will run. Priorities range from 1 to 10, with priority 1 callbacks being the first to run at any event and priority 10 being the last.

When writing a plugin, think about how it relates to other plugins. Your plugin might be a fairly gentle transformation upon the data, or it might be something more dramatic, which leaves the data in a state that other plugins won't be able to use. The more dramatic plugin will want to use a high priority for its 'save' callback,

Error handling

When a plugin callback dies, MT will continue and will call other callbacks.

Note that if you have one callback that relies on another having returned successfully, you should be prepared if for some reason one callback doesn't in fact run. For example, if you have symmetrical callbacks that run respectively on load() and on save(), and the save callback fails, the data in the database may not be in the form expected by the load callback.

Text Filters

Movable Type offers users a extensible selection of text filters to assist in composing entries with formatting properties. Rather than author an entry in HTML, a user can choose a text filter which will transform the text of the entry, replacing certain symbols with sophisticated formatting commands. Text filters appear in a pop-up menu on the entry-editing screen, so text filters are chosen on an entry-by-entry basis.

Adding a Text Filter

A text filter is added by calling MT->add_text_filter(), as follows:

MT->add_text_filter($key, {label => $label, 
                            on_format => <executable code>});

$label is the human-readable text which identifies the filter to the user; this text appears in the pop-up menu on the entry-editing screen. $key is an identifier that will be used as an HTML name attribute, and the filters in the menu are sorted alphabetically by their $key values.

The value passed in the value of the on_format key is a code reference. This is the code reference that will be called to transform the entry text before displaying it.

The code reference is called everywhere the entry is displayed, except in the entry-editing screen itself. This includes the entry preview, the result of the <MTEntryBody> tag, and in a TrackBack ping or newsfeed.

Adding an Action to a Movable Type Application Page

Some of the pages in Movable Type's application interface list objects of some type, or allow users to edit an object. Many of these pages admit natural extension in the form of additional actions that the user may wish to make on those objects.

To add an action to one of these pages, call the add_plugin_action method of the MT class:

MT->add_plugin_action('entry', $link,
                      'Add one xyzzy monster to this entry');

Here, entry indicates the page on which the link should appear. Passing asimple object type indicates that the action link should appear on the 'edit' page for an object of that type. Other values that would be valid here include comment, category, template and author.

Additionally, passing one of the following values in the first argument will place the action link on one of the object lists: list_comments, list_commenters, list_entries.


Copyright © 2001-2004 Six Apart. All Rights Reserved.