The Plugin Walkthrough: CGIs & Interfaces
Last time, I showed you how to register your plugin with Movable Type such that it shows up on the Plugins Listing under System Overview with some essential links, for example, a link to the documentation and the author's website. I also showed you how to use some of the new API in Movable Type 3.2 to allow users to configure settings in your plugin.
Today we'll be looking at the different ways your plugin can provide an interface whether it be through providing a separate cgi file or by using some of the new APIs in MT 3.2.
Before we get started, I suggest you download the latest version of HelloWorld so you can follow this tutorial.
First we will examine how to create a cgi file to provide the interface for our plugin. With Movable Type 3.2, Six Apart streamlined the process of creating cgi files for our plugins. As a result, plugins/HelloWorld/helloworld.cgi looks like this:
#/usr/bin/perl -w use strict; use lib "lib", ($ENV{MT_HOME} ? "$ENV{MT_HOME}/lib" : "../../lib"); use MT::Bootstrap App => 'HelloWorld'; __END__
The first line is called the hashbang (or shebang) and tells the webserver that this is a cgi file and must be executed. The second line, use strict should always be used as it helps identify many errors with your coding (such as syntax issues). The third line is where the magic starts, it basically includes all the lib folders such that Perl can find the necessary modules to run your script. Now before we look at the final line of code, I'm going to explain the double colon notation for those that don't understand it. For those that do, forgive my self-taught explanation!
The double colon notation is simply a way of identifying where certain chunks of code can by found, typically it means the thing on the right of the double colons (it could be either a file or routine name) is found in the thing to the left of the double colons (usually a folder or file name) somewhere in @INC which is an array (list) of all lib and extlib folders. There's a good chance that made no sense so lets have a few examples:
MT::BootstrapmeansBootstrap.pmis found in theMTfolder somewhere in@INC(and it is inMT_DIR/lib/MT/Bootstrap.pm)MT::App::CMS::meansCMS.pmis found in theAppfolder which is in theMTfolder somewhere in@INC(and it is inMT_DIR/lib/MT/App/CMS.pm)MT::App::CMS::list_commentsmeans thelist_commentsroutine can be found in the fileCMS.pmwhich is in theMTfolder somewhere in@INC
Now that's probably not the official explanation but having self-taught myself Perl, I hardly ever know the official explanations! So back to helloworld.cgi, the fourth line of code looks like this use MT::Bootstrap App => 'HelloWorld'; and basically tells MT::Bootstrap to run the application code found in HelloWorld which in this case is found in MT_DIR/plugins/HelloWorld/lib/HelloWorld.pm and the rest is handled by Movable Type. The final line in helloworld.cgi is __END__ which signifies the end of our code.
So lets take a look at HelloWorld.pm to see what the application code is.
package HelloWorld; use strict; use base 'MT::App'; sub init { my $app = shift; $app->SUPER::init(@_) or return; $app->add_methods( view => \&view, ); $app->{default_mode} = 'view'; $app->{requires_login} = 1; $app; } sub view { my $app = shift; my $q = $app->{query}; my $plugin = MT::Plugin::HelloWorld->instance; my $config = $plugin->get_config_hash('system'); my $param = { }; $param->{text} = $q->param('text') || $config->{text}; $param->{version} = $MT::Plugin::HelloWorld::VERSION; $app->add_breadcrumb('Main Menu', $app->{mt_url}); $app->add_breadcrumb('The Plugin Walkthrough', $app->uri('mode' => 'view', args => { 'text' => $param->{text}} )); $app->add_breadcrumb('The Interface'); $param->{breadcrumbs} = $app->{breadcrumbs}; $param->{breadcrumbs}[-1]{is_last} = 1; $app->build_page('view.tmpl', $param); } 1;
The first line defines the name of this package which enables us to use the double colon notation from above and use strict is present as always. The third line sets the the base of this app as MT::App, which saves time as we inherit all the routines in MT::App and thus can use them in our app!
Next comes the init subroutine. This routine is the first thing that is run whenever helloworld.cgi is called in your browser and sets up our application at the very basic level by defining what your application is capable of and whether people need to be logged in. The first two lines of the routine should always be left intact as they populate $app with the defaults from MT::App. The next three lines are what matters. $app->add_methods is a hash of the various modes in your application and these are typically accessed through the __mode url parameter. In HelloWorld.pm I've added the method view which points to the view subroutine. This means that when I go to MT_DIR/plugins/HelloWorld/helloworld.cgi?__mode=view the code in the view subroutine gets executed. The next line, $app->{default_mode} allows you to specify a default mode which is automatically displayed when the __mode url paramter isn't specified. The last line in this subroutine decides whether someone needs to be logged into Movable Type to be able to use the plugin. The init routine is called everytime your app is run and therefore should be as lean as possible.
Lets look at the view subroutine:
sub view { my $app = shift; my $q = $app->{query}; my $plugin = MT::Plugin::HelloWorld->instance; my $config = $plugin->get_config_hash('system'); my $param = { }; $param->{text} = $q->param('text') || $config->{text}; $param->{version} = $MT::Plugin::HelloWorld::VERSION; $app->add_breadcrumb('Main Menu', $app->{mt_url}); $app->add_breadcrumb('The Plugin Walkthrough', $app->uri('mode' => 'view', args => { 'text' => $param->{text}} )); $app->add_breadcrumb('The Interface'); $param->{breadcrumbs} = $app->{breadcrumbs}; $param->{breadcrumbs}[-1]{is_last} = 1; $app->build_page('view.tmpl', $param); }
Every method in our app is passed the results of the init_app routine so first off we grab that with $app = shift;. The variable $q is a convinient way of accessing all the URL parameters so I always define that second. If you remember,I'd said last time that there was good reason we defined a package in our pl file and you can see the fruit of that here. When combined with an instance routine in the pl file, we can quite easily get our config data using the built in get_config_hash method. By default it returns you system wide configuration data but this can be overriden by passing it a blog scope blog: $blog_id like so $plugin->get_config_hash('blog: 1'); which would return configuration data for blog #1. Note, this only works if you are setting blog level configuration data so in the case of HelloWorld this wouldn't work as we're only storing data from system_config_template (i.e. system wide configuration data)
Next we create the hash $param that stores our HTML::Template tags and populate it with a few values. You'll see that for $param->{text}, I give it the value $q->param('text') || $config->{text}. What this means is that if we specify a URL parameter called text, then our template tag text is given that value, if there is no text URL Parameter then it defaults to our configuration value. This means if you go to helloworld.cgi?__mode=view&text=Foo then $param->{text} contains Foo otherwise it uses the value in the configuration (either HelloWorld or GoodbyeWorld).
We also populate the breadcrumbs that appears at the top of the pages in Movable Type as well as in the browser's titlebar. For the second breadcrumb The Plugin Walkthrough, you'll see I'm using the $app->uri method which looks complicated but is fairly easy. $app->uri gives you a URL of your app script (in this case a link to helloworld.cgi) and to ensure maximum compatibility you pass it the URL parameters. You can add any number of breadcrumbs in the same way - to add a new breadcrumb simply use $app->add_breadcrumb('Page Name','Page Link');
Finally we tell Movable Type to parse and display the template called view.tmpl. To locate the correct template, Movable Type first looks in the plugin's tmpl folder (typically located at MT_DIR/plugins/PLUGIN_NAME/tmpl/) to see if it can find the file, if not then it looks in MT's tmpl folder (typically MT_DIR/tmpl/cms/) and if it can't find the template in either locations then it throws out an error message. This means that your plugin templates can be named similar to Movable Type's templates and you don't have to worry about conflicts.
The actual template is fairly simple:
<TMPL_INCLUDE NAME="header.tmpl"> <h3><MT_TRANS phrase="The Movable Type Plugin Walkthrough"></h3> <div class="message"><TMPL_VAR NAME=TEXT> <MT_TRANS phrase="version"> <TMPL_VAR NAME=VERSION> </div> <p><input value="« Return" onclick="window.location = '<TMPL_VAR NAME=MT_URL>?__mode=list_plugins'" type="button" /></p> <TMPL_INCLUDE NAME="footer.tmpl">
The first line includes the template called header.tmpl. As there's no header.tmpl in MT_DIR/plugins/HelloWorld/tmpl/, Movable Type uses the one found in MT_DIR/tmpl/cms/. This template provides the Movable Type logo, the sidebar and everything until the breadcrumbs. It is highly recommended you keep this line in to ensure your app fits in with the rest of MT. If you want to tweak some elements for your app, simply create a new template called header.tmpl, copy MT_DIR/tmpl/cms/header.tmpl and add your changes and save this new template in your plugin's tmpl folder.
The actual template is simple XHTML markup that uses HTML::Template tags to display various things on the page. By default, there are a few tags that you can use:
<TMPL_VAR NAME=SCRIPT_URL>- this gives you a link to the app's URL, in this case it is a link tohelloworld.cgi<TMPL_VAR NAME=MT_URL>- it is recommended you use this tag when you want to link back to the main Movable Type app rather than hardcodingmt.cgias the name of theAdminScriptcan be changed inmt-config.cgi.<TMPL_VAR NAME=STATIC_URI>- this provides a link to the location of the static files (images, javascript and styles)
One thing you'll notice is that I've wrapped English parts of the template with <MT_TRANS phrase="">. This tag allows easy localization of your app (something we'll discuss later in the walkthrough) and as a result I highly recommend you use it for any hardcoded English.
Finally at the end of the template, we include footer.tmpl which provides the closing markup for the template as well as the copyright notice. You can add your own copyright notice by creating a duplicate footer.tmpl for your plugin and placing it the plugin's tmpl folder.
The final piece of the puzzle is to ensure that your application is easily accessible from Movable Type. The easiest way to do this is to specify a config_link key/value pair for your plugin object whilst registering your plugin.. The config_link value should just be the name of your cgi file (with any url parameters), do not include any path details as Movable Type calculates this automatically. In helloworld.pl you can see config_link => 'helloworld.cgi?__mode=view'
Why Bother ?!
In some cases, for example if you don't have many methods in your app or if you're extending functionality already found in Movable Type, it may be overkill to create a cgi. As a result, with Movable Type 3.2, your plugin and hook directly into mt.cgi and add custom methods without creating a cgi! Open up helloworld.pl and you'll see a new subroutine called init_app:
sub init_app {
my $plugin = shift;
my ($app) = @_;
return unless $app->isa('MT::App::CMS');
$app->add_methods(
helloworld => sub { in_view($plugin, @_); },
);
}
This routine is similar to HelloWorld::init (the init routine in HelloWorld.pm), we first grab our plugin object $plugin and an app object, $app. The third line is something very interesting. When you have an init_app routine in a pl file, it is called every time any Movable Type script is run whether it be mt.cgi, mt-comment.cgi or mt-trackback.cgi. With the HelloWorld plugin, I just want to add a method to mt.cgi and as a result, its a waste of processing power if it is run for other scripts. In English, the third line says "If I'm being run by mt.cgi you can continue otherwise do nothing". This can be adapted for different scripts, for example if you only wanted init_app to run during the commenting process, you'd use return unless $app->isa('MT::App::Comment'); instead (each script has its own app module which can be found by opening the cgi script in a text editor and looking at the value for the App key passed to MT::Bootstrap).
The rest of the subroutine is functions exactly like HelloWorld::init except this time, the methods are being added to mt.cgi so you'll want to make sure you do not create conflicting methods. With this plugin, I've got a new mode which I can access by mt.cgi?__mode=helloworld. You'll notice that the method points to an inline routine which then calls the in_view subroutine in the pl file. The reason this is done is to easily pass $plugin and the app object.
With MT::Plugin::HelloWorld::in_view, the plugin and app objects are passed to it so we can just grab them with my $plugin = shift; my ($app) = @_;. The rest of the subroutine functions the same until we get to building the page where the only difference is we have to load the template first, so it becomes $app->build_page($plugin->load_tmpl('view.tmpl'), $param);
If you go down the route of adding methods to mt.cgi, the config_link value you specify whilst registering your plugin becomes slightly different - config_link => ../../mt.cgi?__mode=helloworld
With the HelloWorld plugin, I've implemented both techniques such that you can run MT_DIR/plugins/HelloWorld/helloworld.cgi and mt.cgi?__mode=helloworld and you should get something that looks similar to

With Part III of the Walkthrough, we'll dive into some of the new API that comes with Movable Type 3.2 including Plugin Actions and Itemset Actions - what they are, how to use them, when to use them and what the difference between the two are.

Dan Wolfgang said:
on Jan 18, 2006 9:37 PM | Reply
I enjoyed this article, Arvind, but it's causing me to grow impatient--I want more! Part 3? Please? :)