Extending Movable Type Using a Pinged Script
I find myself in a constant struggle between accepting Movable Type for what it is, and working to extend it. There are a few cases where I want to do interesting things with entries, but I don’t want to hack into Ben's Perl code.
I solved this problem by inserting just enough code to ping a specified URL whenever an entry is saved. Not just when a new entry is added (like the standard ping), but whenever an entry is saved, whether it’s being added or edited, in draft or published.
The file I modified is:
This is the entry class file. At line 286, you’ll find a subroutine called “Save.” As near as I can tell, this subroutine is run every single time an entry is saved. Inside this subroutine (at the bottom, so it runs after the entry has been completely saved), I placed this code:
use LWP::UserAgent; use HTTP::Request::Common; my $ua = LWP::UserAgent->new; $ua->request(GET '[URL to ping]?e=' . $entry->id);
(I’m not a Perl hacker. I managed to cobble this together by luck alone. If you know Perl, and there’s a better way to write this, please let me know.)
This code will call the specified URL (hereafter, the “Catcher”) with the ID of the entry being saved as the value of the “e” querystring argument. The Catcher is written in PHP – which I’m infinitely more comfortable with – and it checks the IP on the request to ensure it’s coming from the same machine (though someone manually calling this page probably wouldn’t hurt anything, depending on what you’re doing with it).
Yes, I know – it’d probably be easier to just run a script from the command line and pass in the entry ID as an argument. However, I don’t own the server, and you never know how someone else’s machine is going to react to something like that. Of course, HTTP calls have their own issues (more below).
The Catcher can do anything. It has the entry ID, so it can run a database query on the “mt_entry” table, retrieve the entire entry and all the category placements, and…well, do anything you need it to. A couple possibilities:
Making Enhanced Search Simpler
Say I’m running search on my site via a SQL query. Every search is run against (1) the title and keywords (together), (2) the body, and (3) the comments. This is resource intensive (the comments require a separate SQL call), and I can’t strip out the HTML in SQL (well, I can, but it’s painful) so entries that have matching text in HTML tags get returned as well.
The Catcher page can fix this problem. Every time an entry is saved, I can update an “extension” table – the title and keywords get concatenated and dumped in one column, the body gets all its HTML stripped (via PHP’s “strip_tags” function) and is dumped in another column, and the the body of all the comments get strung together and dumped in a third column. The entry ID itself gets dumped in another column.
Searches can now be run against this one table (joined to “mt_entry” via the entry ID present in both tables), and the results are simple and easy to work with. (Incidentally, I’ll be implementing the described search method on this site soon – it was the reason I came up with this idea in first place.)
Using Discussion Forums for Comments
…you could probably hack this system up. Set one of your ping URLs to be a script that queries the MT database for new posts and enters a topic for it on the board.
The Catcher can do this. You just need a two-column “bridge” table to connect MT entry IDs to Invision topic IDs (if you’re brave, you could just add a column to Invision’s “topics” table – but if you’re that brave, then just write everything in Perl somewhere in the MT code itself).
Using the entry ID passed in by the ping, check if it exists in the bridge table. If not, then it’s new and it needs an Invision topic. Enter a new Invision topic (using a manual SQL call), then enter that topic ID and the MT entry ID in the bridge table. Viola – you have an Invision topic to handle comments and it’s linked to the MT entry.
The only issue I have with the code right now is that it will hang while it waits for a response from the Catcher. This isn’t a problem if everything is working, but if the Catcher has issues, then the MT editor appears to hang. You can put a timeout on the HTTP call, but then you run the risk of the Catcher not running and you not knowing about it. These issues, however, can be managed with good error handling and good logging.
So, the system isn’t perfect, but it works as well as it needs to. It’s a simple method of extending and integrating MT that requires as few changes as possible to the MT code base.