WordPress pingback and trackback bug

Tuesday, 26 July 2005

There is a bug in the way WordPress 1.5.1 handles draft posts and Advanced Editing mode. One effect of this is that pingbacks and trackbacks are sometimes sent with the wrong URL. This problem has been around for a while, but it looked a bit complicated so I figured that somebody would fix it one day. But as I was trying to write a useful new plugin, the bug hit me. So I have investigated the bug and found out what it’s all about. I have also written a plugin that fixes the problem.

Background

Basic (unfancy) WordPress permalinks look like this:
www.blog.com/index.php?p=42

If you have fancy permalinks enabled, you’ll have fancy permalinks that look like this:
www.blog.com/2005/07/11/ecky-thump/

If you publish a post and send pingbacks or trackbacks to other blogs, your permalink URL is sent as part of the ping/trackback.

The Problem

If you create a post and save it as a draft, and then publish it, then WordPress will send pingbacks and trackbacks with the unfancy URL instead of the fancy one. The same thing happens if you create a post and click “Advanced Editing” before publishing it. (Behind the scenes, WordPress creates a draft when you do this.)

This bug is in the WordPress bug tracking systems as bug #1257.

The Quick Solution

I have written a plugin that fixes this. The FixBack WordPress plugin page has details and download.

Analysis: The Cause

The problem comes from the get_post() function. This function maintains a cache of posts. However, it never updates cache entries. In cases where WordPress is simply reading from the database to display posts, this is fine. But when publishing a post there can be problems. The problem occurs when we publish a post that was originally a draft.

In this case, get_post() populates its cache with the draft version of the post (which has status set to ‘draft’). When the trackback/pingback code calls get_permalink(), get_permalink() gets the post data from get_post() and thinks the post is still a draft — and get_permalink() returns unfancy permalinks for draft posts.

Analysis: The Solution

The solution I have used in my plugin is to update the cache after publishing the post, but before doing the trackbacks and pingbacks. Fortunately, there is a convenient action hook in just the right place. (By an amazing coincidence, I am partly responsible for the hook being where it is — what a stroke of luck.)

The action hook is ‘edit_post’. FixBack simply hooks into this, and updates the post cache by re-reading from the database. That’s all!

Further thoughts

This problem may be widespread in WordPress. I know many functions in WordPress maintain internal caches that could suffer from similar problems. The FixBack plugin only fixes this one case. This functionality should be added to the core. The simplest way is to add a function that invalidates the cache for a particular post ID, and to call this function immediately after publishing the post.

It would be even nicer to implement a layer (a class perhaps) that mediates all post data access — this could then maintain its cache without worrying that somebody will bypass it. Following this sort of approach throughout would be quite a big effort — WordPress 2.0?

Gory details

This took me quite some time to figure out. Here I have annotated some excerpts from the WordPress source files. This may illuminate or obscure the foregoing explanations. The two colours of annotation describe the problem and the plugin solution respectively.

File: wp-admin/post.php

Line 271
case 'editpost':
	...
	The next line calls user_can_edit_post(), which calls
	get_post(), which reads the draft post from the database
	into $post_cache. The post status is 'draft'.
	if (!user_can_edit_post($user_ID, $post_ID, $blog_ID))
		die( __('You are not allowed to edit this post.') );
	...
	$post_status = $_POST['post_status'];
	...
Line 364
	The next line updates the post in the database. This
	updates the post slug and sets the status to 'publish'.
	$result = $wpdb->query("
		UPDATE $wpdb->posts SET
			...
	Now the post is published, so its slug should be set
	and its status should be set to 'publish'.
Line 430
	if ($prev_status != 'publish' && $post_status == 'publish')
		We could use this hook to refresh the cache, and
		this would fix the problem with publishing drafts.
		But the stale cache may have other effects, so
		let's delay the refresh until the next line...
		do_action('private_to_published', $post_ID);

	Here is where we install a plugin that refreshes
	$post_cache from the database. Then in the next few
	lines, pingbacks and trackbacks will use the correct 
	permalink URL. Also, any plugins using the 'publish_post'
	hook will be able to call get_permalink() with
	no problems.
	do_action('edit_post', $post_ID);

	if ($post_status == 'publish') {
		do_action('publish_post', $post_ID);
		Here is the problem. The trackback and pingback
		routines call get_permalink(), which returns the
		unfancy permalink.
		do_trackbacks($post_ID);
		do_enclose( $content, $post_ID );
		if ( get_option('default_pingback_flag') )
			pingback($content, $post_ID);
	}

File: wp-includes/template-functions-links.php

Line 25
function get_permalink($id = 0) {
	...
	Next line gets the post from $post_cache. When the cache was
	filled (see above) the post slug was empty and the status was
	'draft'. Even though both are now updated in the database,
	the cache is stale.
	$post = & get_post($id);
	...
	if ('' != $permalink && 'draft' != $post->post_status) {
		... (do the fancy permalink)
	} else { // if they're not using the fancy permalink option
		We end up here because the cached post status is
		'draft', though it should be 'publish'.
		$permalink = get_settings('home') . '/?p=' . $post->ID;
		return apply_filters('post_link', $permalink, $post);
	}
}

Tags:

29 comments

You can leave a comment, or trackback from your own site.

  1. Fantastic beat ! I wish to apprentice whilst you amend your web site, how could i subscribe for a blog website? The account helped me a appropriate deal. I had been a little bit familiar of this your broadcast offered shiny clear idea

Leave a comment