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
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.
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.");
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.
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 };
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
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>
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.
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.
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,
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.
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.
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.
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
.