I’ve been waiting for a consumer version of Movable Type 3 before taking a look at it, having been bitten by a couple of bugs in the developer version and the lack of (the now award-winning) MT-Blacklist. Both will soon be alleviated in the upcoming Movable Type 3.1, scheduled for August 31, which means it may or may not be already in beta.
Reason enough to look again at MT3 and the plugin architecture. I had an idea for a plugin, and it took all of one hour to write up. (More on that later.) But then I wanted to be able to use a CGI to set a couple of configuration variables. And there I got stuck. The plugin tutorial says that “The best way to (build a human interface for your plugin) is to write a subclass of MT::App” without really explaining how to do that. The docs for MT::App are a bit sparse on the subject as well. There are a number of mature plugins that build interfaces in this way, but looking at them, I felt like I was looking at the blueprints of the Empire State Building, when at first all I want to build is a hut on the beach.
Finally when I was looking at Chad Everett’s MT-Notifier (also award-winning) did the light bulb turn on inside my head and I saw the light. It took me 4 files, each in a different directory, but I was able to write a Hello World CGI plugin for Movable Type 3.
First we need a MT_DIR/plugins/hello.pl. It doesn’t do anything except register the plugin so I get a nice link to my CGI on the MT main page.
package MT::Plugin::Hello;
use strict;
use MT;
use MT::Plugin;
use vars qw($VERSION);
$VERSION = '0.1';
my $about = {
name => 'MT-Hello',
config_link => '../mt-hello.cgi',
description => 'Say hello to the world in a unique way.',
doc_link => 'http://www.papascott.de/archives/2004/08/12/hello-world-as-an-mtapp-cgi/'
};
MT->add_plugin(new MT::Plugin($about));
Next comes the CGI itself in MT_DIR/mt-hello.cgi, which does nothing but call my subclass of MT::App. Most plugin CGIs look exactly like this.
#!/usr/bin/perl -w
use strict;
my($MT_DIR);
BEGIN {
if ($0 =~ m!(.*[/])!) {
$MT_DIR = $1;
} else {
$MT_DIR = './';
}
unshift @INC, $MT_DIR . 'lib';
unshift @INC, $MT_DIR . 'extlib';
}
eval {
require shanson::hello;
my $app = shanson::hello->new (
Config => $MT_DIR . 'mt.cfg',
Directory => $MT_DIR
) or die shanson::hello->errstr;
local $SIG{WARN} = sub { $app->trace ($_[0]) };
$app->run;
};
if ($@) {
print "Content-Type: text/htmlnn";
print "An error occurred: $@";
}
Now we get to the meat and potatoes in MT_DIR/extlib/shanson/hello.pm, where I say that all I really want my $app to do is print some text. I require a login only so I get a pretty header on the page. Update: I’ve incorporated Phil Ringalda’s suggestions. The sub uri is so the pretty header links back to the proper mt.cgi instead of mt-hello.
package shanson::hello;
use strict;
use MT::App::CMS;
use vars qw(@ISA $VERSION);
@ISA = qw(MT::App::CMS);
$VERSION = '0.1';
sub uri {
$[0]->path . MT::ConfigMgr->instance->AdminScript;
}
sub init {
my $app = shift;
$app->SUPER::init (@) or return;
$app->add_methods (hello => &hello);
$app->{default_mode} = 'hello';
$app->{requires_login} = 1 ;
$app->{user_class} = 'MT::Author';
$app->{is_admin} = 1;
$app;
}
sub hello {
my $app = shift;
my %param = (no_breadcrumbs => 1);
$app->build_page('hello.tmpl', %param);
}
Finally, I put a template file in MT_DIR/tmpl/cms/hello.tmpl, so my class knows what it is supposed to print.
<tmpl_include NAME="header.tmpl"> Hello, world! <tmpl_include NAME="footer.tmpl">
And no description of a web application is complete without a couple of screenshots:
Now I just have to write a CGI that actually does something, like, ummm, what did I want to do again? Oh yes, set a couple of configuration variables for my plugin.
Update 28 Oct 2004: David Jacobs noticed some missing backslashes, which I’m guessing went missing when I reimported all my blog entries into Movable Type. Sorry about that!
18 Mar 2005 MySQL ate my blackslashes again! Crucial backslashes were missing in $app->add_methods (hello => &hello); and $app->build_page('hello.tmpl', %param);. Thanks to the ProNet mailing list for pointing this out in February and to Nathanial Irons for reminding me that I hadn’t yet corrected this page.

{ 9 comments }
Wonderful! Not that, er, I’ve been too lazy to either figure it out on my own, or even to steal from someone else, you understand. Just, generally speaking, how very nice of you!
Couple of nits that I would have probably noticed, if I was stealing it for my own use: hello.pm’s missing the “sub ” before init, and the tmpl doesn’t need the end tags for body and html, since footer.tmpl already closes them.
Also, depending on your app, you might want to add script_url => MT::ConfigMgr->instance->AdminScript to %param in hello.pm, so that the “Main menu” and “Logout” links in the header go to mt.cgi, instead of mt-hello.cgi. Since you’re an MT::App, you can certainly handle them, but it looks weird when hours later you are posting an entry through mt-hello.cgi.
Thanks for the nits, Phil! I’ll incorporate your changes.
As for the script_url, I went back to Notifier to see how Chad did it, and saw that he defined a sub uri that does the same thing. It has the advantage that it gets picked by anything else we might add to &app->add_methods. Now that I know what it’s for, I’ll put it back in.
One thing you may want to consider: The uri subroutine that I wrote in MT-Notifier does do what you expect, in that it overrides the default one that uses the calling script name (mt-hello.cgi in your case).
However, in at least one case, you’ll actually want to use your script name instead. Otherwise it will end up calling mt.cgi, which isn’t what you want.
This case occurs when you have not yet logged into your MT install and want to go directly to mt-hello.cgi. When that is the case, you’ll notice that the redirect info will send you back to mt.cgi, which means you’ll need to select your script again. Having logged in, it should work that time. But it’s an extra step, and oh how I hate extra steps.
For this particular plugin, it may not matter. For MT-Notifier, I like to be able to go straight there, instead of having to go back to the menu and select to go to MT-Notifier again – I already told it that!
Here’s that code (contents of sub uri):
$[0]->path . ($[0]->{author} ? MT::ConfigMgr->instance->AdminScript : $_[0]->script);
To get around this particular case, I use a condition in that statement. Specifically, I checked to see if the author is set. If it is, it means someone is logged in. In that case, use the AdminScript. If it is not set, then someone is not logged in, which means I’d actually want to use my script for the redirect. Hope that makes sense to everyone.
My solution to the login redirect problem was a hack of the __script parameter: $app->{__script} = $app->{cfg}->AdminScript; Then I created separate routines and template variables for the path to my CGI (as opposed to the path to mt.cgi), and used those when appropriate in my templates. This allowed me to include the blog-specific MT sidebar in my UI (because my config was blog-specific, so it ought to look like part of the blog-specific interface).
Also, I believe config_link => ‘../mt-hello.cgi’ in hello.pl will cause the config link to appear as “http://yoursite/mt/plugins/../mt-hello.cgi”, which ought not to work for most web servers. (If it does work, it’s a security hazard, because then people can reach around your filesystem for files.) Because of this limitation, MT 3.01 requires that CGIs be under the plugins/ directory somewhere, which means hard-coding a relative path to the MT root directory (which means the plugins/ directory must be in the MT app root, which I consider undesirable).
I did test the hello plugin with MT 3.01 (Apache 1.3x, FreeBSD), and the ‘../mt-hello.cgi’ notation worked just fine. At least for Apache, the double-dot notation is not a security risk, since it will not break out of your DocumentRoot no matter how many double-dots you try to insert.
That said, the idea of putting all plugin files, be they CGIs, libraries, or whatever, under plugins/ is appealing to me, as opposed to plastering files all over the MT directory structure. Make the plugin author work to find the files he needs, not the poor end-user!
I’m glad to hear Apache does the useful thing with the double-dot. I’ll have to figure out why I couldn’t get it to work in my set-up.
As for putting everything in the plugins directory, my point is that nothing should be in a web servable directory that isn’t intended to be served. Without special configuration, most web hosting installations would serve *.pl files as if they were text files, and exposing their contents is a potential security risk. A site admin who knew what they were doing could explicitly configure their web server to prevent plugin source files from being served, but for most users, we have to assume they won’t or can’t take such precautions. Most people won’t know that these files are servable, and not knowing could lead to problems. I can imagine a developer building a site with custom plugins that access a database, with the database login and password embedded in the *.pl file. In general, it’s just an unnecessary security burden that the admin needs to keep track of, and I believe it could be avoided.
The same could be said about mt.cfg, but I’m willing to concede that because it will always be just the one file, the instructions to secure it are in the MT installation instructions, and most alternatives would be complicated and platform specific. For my site, I also lock out the template directories, lib, extlib, and anything else that I don’t explicitly want dished up by the server.
I agree that it’s appealing to package and distribute plugins with instructions that just say, “Put all this in the plugins directory.” (And similarly as an admin, so plugin files are scattered everywhere.) I just want the option for admins to be able to move the not-to-be-served stuff, so as an admin I have the choice, and as a plugin developer I don’t have to worry about it.
My apologies for getting off topic.
Thanks for the hello-world MT::App, this will be useful to many.
Line 5 of mt-hello.cgi should read:
if ($0 =~ m!(.*[/])!) {
instead of
if ($0 =~ m!(.*[/])!) {
One more typo:
print “Content-Type: text/htmlnn”;
should be \n\n …
Thanks for the information. This is very useful
Comments on this entry are closed.
{ 3 trackbacks }