Patching MT 3.2

This post describes the changes I have made to my Movable Type 3.2 installations. Some of these are bug fixes and some are enhancements.

lastn for MTArchiveList (bug)

The “lastn” attribute for the MTArchiveList tag is broken. The behavior is that it only includes roughly one third of the expected elements. The problem is that the index used to count the elements is incremented three times inside the loop.

File lib/Template/ContextHandlers.pm, line 2167 from

                $last = 1 if $n && $i++ >= $n-1;

to

                $last = 1 if $n && $i >= $n;

Line 2184:

		last if $n && $i++ >= $n-1;

to

		last if $n && $i >= $n;

Line 2209:

		    $last = 1 if $n && $i >= $n-1;

to

		    $last = 1 if $n && $i >= $n;

Comment posting filters

This modification provides support for doing comment filtering before the comment is posted. Any errors send the commentor to the Comment Error page rather than junking or moderating the comment. There is an open question of whether this is best done by server side filters or browser side Javascript. This mechanism, which is a server side one, has two advantages:

  • It re-uses existing mechanisms and techniques, i.e. filtering callbacks, plugins and Perl.
  • It doesn’t require Javascript to be enabled in the client browser.

Modify lib/MT/App/Comments.pm, around line 299, in subroutine post, immediately before the lines

    $comment = $app->eval_comment($blog, $commenter, $comment, $entry);
    return $app->preview('pending') unless $comment;

insert

    my @reasons;
    MT->run_callbacks('MT::Comment::validate_filter', $app, $comment, \@reasons) ||
        return $app->handle_error(join('',@reasons));

This is sufficient, although it makes the interface to the filters awkward and non-standard. It is better to do another modification to get a clean, standard interface for the filters.

Modify lib/MT/Comment.pm. Add the following subroutine anywhere, although at line 204, at the end, seems like a good spot. This intercepts callback registration and wraps the “validate_filter” ones to improve the interface.

sub add_callback {
    my ($self, $tag, $order, $plugin, $code) = @_;
    if ($tag eq 'validate_filter') {
        my $tmp = $code;
        my $wrapper = sub {
            my $log = pop @_;
            my ($flag, $text) = &$tmp(@_);
            push @$log, $text if !$flag && $text;
            return $flag;
        };
        $code = $wrapper;
    }
    return $self->SUPER::add_callback($tag, $order, $plugin, $code);
}

That’s it. You can now add filtering callbacks to comments that operate when the commentor hits the “Post” button and, if the comment is filtered, sends the commentor to the Comment Error page. The interface is that the callback gets three arguments:

  1. The callback object, which is used to report internal errors.
  2. The MT Application object (which will be an instance of MT::App::Comments), which can also be queried for CGI parameters.
  3. The comment object, which is an instance of MT::Comment.

The return value should be either

  • A scalar value which is true, indicating that the comment passed the filter (no problems).
  • A list of
    • A false scalar value, indicating that the comment did not pass the filter.
    • A string describing the reason the comment did not pass the filter.

The set of all strings from failures for all filters is available for display to the user via the MTErrorMessage tag in the Comment Error system template.

Here is an example:

sub name_required {
    my ($ctx, $app, $comment) = @_;
    if (not $comment->author) {
        return (0, 'You must provide a name in order to comment.');
    }
    1;
}

require MT::Comment;
MT::Comment->add_callback('validate_filter', 5, $plugin, \&name_required);

This presumes that $plugin is set to a properly registered MT plugin. This filter will check for a commentor name and, if not present, will reject the post with the message “You must provide a name in order to comment.”. This is useful if you want to not require email addresses but still require names (not possible in the distribution). This is currently running, so try it out by trying to post a comment without a name.

NOTES: You could use this to modify comments as well as filter them, in which case you would want to intercept preview comments as well. Hmmm. That’s probably better done via text filters and FilterStack.