Working with 'custom post types', I ran into a snag. It did not seem to be as plug-and-play as it has been depicted on many many articles. Consider the following for WP3.0:
function bmrw_init() {
register_post_type('my_custom_pt', array(
'label' => 'My Custom Post Types',
'singular_label' => 'My Custom Post Type',
'description' => 'A new post type.',
'public' => true,
'show_ui' => true,
'_builtin' => false,
'query_var' => false,
'rewrite' => array( 'slug' => 'my_custom_pt',),
'capability_type' => 'post',
'hierarchial' => false,
'supports' => array('title', 'editor', 'author', 'thumbnail', 'excerpt', 'comments', 'custom-fields', 'revisions'),
));
}
add_action('init', 'bmrw_init');
So according to many articles similar to this one, the above should produce permalinks like this:
http://www.my-site.com/my_custom_pt/testing-post/
And also according to those articles, when going to that url, the post should come up all honky-dory. The problem I ran into was that the permalink was getting constructed properly, but when I browsed to the url, I was getting a 404 error.
Like I always do, I started doing some debugging and searching to narrow down the problem. After about 5 hours I found the following.
Looking in wp-includes/classes.php, on line 146, I saw the following code:
$rewrite = $wp_rewrite->wp_rewrite_rules();
This code gets a list of all the search and replace values for custom post types, such that, in theory, a url like this:
http://www.my-site.com/my_custom_pt/testing-post/
should be translated to:
http://www.my-site.com/index.php?post_type=my_custom_pt&name=testing-post&page=1
The problem was, that the translated url was coming out as:
http://www.my-site.com/index.php?post_type=my_custom_pt&name=$1&page=$2
So the rules were not being written properly, in my case. I thought maybe that I just did not have the latest version of WP3.0, so I re-downloaded it, and compared the code. It was identical. So I looked into it a bit further, to find out why these $1 and $2 was not getting replaced.
A bit below the code that retrieves the rules, I found where the replacement should take place:
foreach ( (array) $rewrite as $match => $query) {
// Don't try to match against AtomPub calls
if ( $req_uri == 'wp-app.php' )
break;
// If the requesting file is the anchor of the match, prepend it
// to the path info.
if ( (! empty($req_uri)) && (strpos($match, $req_uri) === 0) && ($req_uri != $request) )
$request_match = $req_uri . '/' . $request;
if ( preg_match("#^$match#", $request_match, $matches) ||
preg_match("#^$match#", urldecode($request_match), $matches) ) {
// Got a match.
$this->matched_rule = $match;
// Trim the query of everything up to the '?'.
$query = preg_replace("!^.+\?!", '', $query);
// Substitute the substring matches into the query.
$query = addslashes(WP_MatchesMapRegex::apply($query, $matches));
$this->matched_query = $query;
// Parse the query.
parse_str($query, $perma_query_vars);
// If we're processing a 404 request, clear the error var
// since we found something.
if ( isset($_GET['error']) )
unset($_GET['error']);
if ( isset($error) )
unset($error);
break;
}
}
Now most of this code is not really that suspicious; however, there is one piece of code that is:
WP_MatchesMapRegex::apply($query, $matches)
This code basically uses the the following regular expression to replace the search values in the parsed url:
// this is not the actual code, just a representation of it
$parsed_url = 'http://www.my-site.com/index.php?post_type=my_custom_pt&name=$1&page=$2';
// this is the actual regular expression
$regular_expression = '(\$matches\[[1-9]+[0-9]*\])';
So basically the replacement utility used to put the values in place of the placeholders is looking for:
'...$matches[1]..'
and our string has:
'...$1...'
There is the problem. So I decided to find what was creating the $1 and $2, because they obviously need to be $matches[1] and $matches[2], in order to actually be replaced.
I searched through the rewrite.php file (the file that produces the rules), and I found that the following function created the $1 and $2:
function preg_index($number) {
$match_prefix = '$';
$match_suffix = '';
if ( ! empty($this->matches) ) {
$match_prefix = '$' . $this->matches . '[';
$match_suffix = ']';
}
return "$match_prefix$number$match_suffix";
}
I determined that because $wp_rewrite->matches was set to '' (nothing), that this function was only putting placeholders like $1 and $2, instead of the proper working version of $matches[1] and $matches[2].
So I came up with a hot-fix for the problem in my 'custom post type' creation function:
function bmrw_init() {
/** new lines for hot-fix start */
global $wp_rewrite;
$wp_rewrite->matches = 'matches';
/** new lines for hot-fix end */
register_post_type('minisite', array(
'label' => 'Mini-Sites',
'singular_label' => 'Mini-Site',
'description' => 'A miniture site about a specific topic inside the main site.',
'public' => true,
'show_ui' => true,
'_builtin' => false,
'query_var' => false,
'rewrite' => array( 'slug' => 'topics',),
'capability_type' => 'post',
'hierarchial' => false,
'supports' => array('title', 'editor', 'author', 'thumbnail', 'excerpt', 'comments', 'custom-fields', 'revisions'),
));
}
add_action('init', 'bmrw_init');
The lines I added, solved the problem. But I don't think that this is a permanent solution to the problem. The only real place that the rewrite engine rewrite.php is called (on a site that has a proper .htaccess file) is in classes.php, on the lines I have pasted here. My long term solution to this problem is to set the default value of $wp_rewrite->matches to 'matches', because the regular expressions replacement tool is the only interpreter of the rewrite rules, and it expects $matches[n].
All this being said, I wonder how I can actually submit that as a bug, or if I completely missed something. If the later is the reason, please tell me what I am doing wrong.
Thanks,
Lou