Overview

The trackback API for Movable Type starting with version 3.2 uses the internal numeric ID of the trackback object as the key. This has two problems:

  • There is no relationship between the trackback key and information in an entry, making clever URL rewriting infeasible.
  • It is an easy target for junkers, who can trivially guess valid values.

This was unavoidable in previous versions of Movable Type, but with the release of 3.2 and its use of globally unique “basenames” for entries, we can do better.

This modification changes the trackback API to use identifying strings instead of numerics as the trackback key. The trackback object identifiers become completely internal (as was done for entry identifiers in 3.2). This overcomes both of the issues listed above.

This project is deployed on multiple several production weblogs, including this one. You can test it out by doing a trackback to this entry using the URL in the “Trackback URL” link below, or using the URL for this entry with “/ping” appended. Please put text that indicates a test if you do test it, so I can clean those out.

NB: This modification explicitly forbids the use of numeric trackback ids. If installed and followed with a complete rebuild, this should not affect legitimate weblogs as the trackback URLs are pulled from your webpages, which will have the new, correct trackback URLs in it.

Notes

This patch is for the 3.3 release of Movable Type. There is a separate patch for version 3.2.

Implementation

Two files in the base distribution must be modified. You can pick up the modified versions of the 3.32 files here or, if you’re paranoid like me, you can do the modifications yourself. Once the files have been modified, you will need to a full rebuild to update all of the trackback URLs. At that point the modified trackback API should be fully operational.

Trackback.pm Modification

The lib/MT/App/Trackback.pm file must be modified. Note that there are two files named Trackback.pm in the distribution, be sure to edit the correct one.

Replace the subroutine _get_params at line 98 with the following subroutine —

sub _get_tb {
    my $app = shift;
    my ($tb, $pass); # Trackback object and password to return
    my ($key, $name); # local vars for internal computations
    my ($entry, $cat); # Target object of trackback.
    if (my $pi = $app->path_info) {
        $pi =~ s!^/!!; # drop leading slash
        my $tbscript = $app->config('TrackbackScript');
        $pi =~ s!.*\Q$tbscript\E/!!;
        ($key, $name, $pass) = split /\//, $pi;
        if ($key =~ m/\d+/) {
            $app->error($app->translate(
                'Numeric trackback IDs are no longer accepted'));
        } elsif (lc($key) eq 'entry') {
            require MT::Entry;
            $name =~ s/\..*$//; # strip any extension
            if ($entry = MT::Entry->load({ basename => $name, status => MT::Entry::RELEASE() })) {
                $tb = MT::Trackback->load({ entry_id => $entry->id});
                $app->error($app->translate(
                    'No trackbacks for entry with basename [_1]', $name))
                    if not $tb;
            } else {
                $app->error($app->translate('No entry with basename [_1]', $name));
            }
        } elsif ($key =~ m/^cat/i) {
            require MT::Category;
            if ($cat = MT::Category->load({ label => $name })) {
                $tb = MT::Trackback->load({ category_id => $cat->id });
                $app->error($app->translate(
                    'No trackbacks for category with label [_1]', $name))
                    if not $tb;
            } else {
                $app->error($app->translate(
                    'No category with label [_1]', $name));
            }
        } else {
            $app->error($app->translate(
                'Invalid trackback target key [_1]', $key));
        }
    } else {
        $app->error($app->translate(
          'Track request URL incorrectly formatted.
           The trackback target path information was missing'));
    }
    ($tb, $pass, $entry, $cat);
}

In subroutine ping around line 147.

Line 152 — change

    my($tb_id, $pass) = $app->_get_params;
    return $app->_response(Error =>
        $app->translate("Need a TrackBack ID (tb_id)."))
        unless $tb_id;

    require MT::Trackback;
    my $tb = MT::Trackback->load($tb_id)
        or return $app->_response(Error =>
            $app->translate("Invalid TrackBack ID '[_1]'", $tb_id));

to

    my($tb, $pass, $entry, $cat) = $app->_get_tb;
    return $app->_response(Error => $app->errstr) unless $tb;

Line 178 — change

    my($blog_id, $entry, $cat);
    if ($tb->entry_id) {
        require MT::Entry;
        $entry = MT::Entry->load({ id => $tb->entry_id, status => MT::Entry::RELEASE() });
        if (!$entry) {
            return $app->_response(Error =>
                $app->translate("Invalid TrackBack ID '[_1]'", $tb_id));
        }
    } elsif ($tb->category_id) {
        require MT::Category;
        $cat = MT::Category->load($tb->category_id);
    }
    $blog_id = $tb->blog_id;

to

    my $blog_id = $tb->blog_id;

Line 209 — change

    no_utf8($tb_id, $title, $excerpt, $url, $blog_name);

to

    no_utf8($title, $excerpt, $url, $blog_name);

Line 209 — change

        $ping->tb_id($tb_id);

to

        $ping->tb_id($tb->id);

Subroutine rss, around line 423.

Line 426 — change

    my($tb_id, $pass) = $app->_get_params;
    my $tb = MT::Trackback->load($tb_id)
        or return $app->_response(Error =>
            $app->translate("Invalid TrackBack ID '[_1]'", $tb_id));

to

    my ($tb, $pass) = $app->_get_tb;
    return $app->_response(Error => $app->errstr) unless $tb;

Subroutine blog around line 465.

Line 423 — change

    if (my ($tb_id) = $app->_get_params()) {
        require MT::Trackback;
        my $tb = MT::Trackback->load($tb_id);
        return undef unless $tb;
        $app->{_blog} = MT::Blog->load($tb->blog_id) if $tb;
    }

to

    if (my ($tb) = $app->_get_tb()) {
        return undef unless $tb;
        $app->{_blog} = MT::Blog->load($tb->blog_id);
    }

ContextHandlers.pm Modification

The file lib/MT/Template/ContextHandlers.pm should be modified. This is not strictly required, but if this is not done then you will have to construct all trackback related data by hand in the templates rather than using the standard tags.

Changes to lib/MT/Template/ContextHandlers.pm

Line 1820 — change

$path . $cfg->TrackbackScript . '/' . $tb->id;

to

$path . $cfg->TrackbackScript . '/entry/' . $e->basename;

Line 1832 — Change

$path .= $cfg->TrackbackScript . '/' . $tb->id;

to

$path .= $cfg->TrackbackScript . '/entry/' . $e->basename;

Line 3381 — change

$path . $cfg->TrackbackScript . '/' . $tb->id;

to

$path . $cfg->TrackbackScript . '/cat/' . $cat->label;

Other Notes, Techniques and Caveats

There are several things to keep in mind once this modification is installed.

Friendly trackback URLs

One of the drivers for this modification was a request for “friendlier” trackback URLs. In particular, the desire was to be able to have other weblogs link via trackback to a post by using the URL of the post with “/ping” appended as the trackback URL. This requires

  • Installing this modification
  • An Apache webserver
  • Access to the Apache configuration file or .htaccess files to enable mod_rewrite

Also note that this works only for individual archives.

To set it up, in the top level directory for the individual archives, place text like the following in a .htaccess file:

RewriteEngine On

# Support trackback pings by appending "/ping" to an individual archive URL.
RewriteRule /([^/]+)/ping$ /cgi-bin/mt/mt-tb.cgi/entry/$1

You need to make sure that the path “/cgi-bin/mt/mt-tb.cgi/” is correct for your installation. This is easy to find, simply look at your current trackback URL.

In theory, this should be easily extensible to supporting category trackback pings, although I never use categories so I have not experimented on it directly yet. Note that the URL must be of the form “…/cat/…” instead of “…/entry/…” for category trackbacks.

NB: The name logic for trackbacks automatically strips extensions, so that you can use both “my_cool_post” and “my_cool_post.html” as the name for the trackback URL. This was done to support this technique.

Trackback URL display

One effect of this modification is to make the trackback URLs noticeably longer. This can cause problems with formatting, particularly of popups. What I have done is change explicit display of the trackback URLs to a link with the link text “Trackback URL”. The reader can then either right-click and use the “copy this link” option (available in FireFox, IE and Opera) or use the “friendlier” trackback URLs.

This project originally inspired by a request from Sherwin Techico on the ProNet mailing list