Dirty hack: force primary category for wordpress post permalink

I’m not especially proud of this hack. However, I was getting crazy by how the category used for a post permalink is choosen. And I could not figure out a clean way to control which is the primary permalink (I hope it’s not part of Wordpress 3.0…).
You may need to set the primary category for permalink in two situations:
- First, you want some consistency among your posts, having them in the same primary category.
- Second, it’s never fun when, by adding an additional category to an existing post, it changes the post permalink!
If you wonder how the “primary” category for a permalink is defined by wordpress, the answer is quite simple: they are ordered by ascending ID. So, if you add an old category to an existing post, your existing post will have a new permalink. And if, like me, you’re using wordpressMu, you’ll discover that a newly added category in a blog may have a very low ID because this category (or tag…) has already been used by any of the other blogs.
The dirty hack
It’s really not a code-hack I’m proud of. However, as I needed something, I believe some others may need it too, that’s why I’m sharing it.
To be on the safe side, I recommend to leave this hack in a standalone file (with my active theme files, in my case), and include it in the functions.php file.
The idea is to alter the “the_permalink” filter. So, let’s add the filter:
add_filter('the_permalink', 'custom_cat_order');
Then, the custom filter function:
// Order by cat_order, otherwise by name, assuming it's a post permalink (dirty!)
function custom_cat_order( $content ) {
global $post;
// in case no post data found, do nothing
if ( @empty($post->ID) ) return $content;
// otherwise, assuming current post is the right post (dirty!)
// custom/primary slug cat order defined here (dirty!)
$cat_order = array("directory", "bonus", "blog");
// add all remaining categories (with duplicates, dirty!)
$all_cats = get_categories();
// Build an array with categories slug
foreach( $all_cats as $cat)
$cat_order[] = $cat->slug;
$cats = get_the_category($post->ID);
if ( $cats ) {
// Build an array with Post categories slug
foreach( $cats as $cat)
$post_cats[] = $cat->slug;
$the_cats = array_intersect($cat_order, $post_cats);
usort($cats, '_usort_terms_by_ID'); // order by ID
$category0 = $cats[0]->slug;
$the_cat = array_shift($the_cats);
// Assuming category is in-between slashes (dirty!)
$content = str_replace("/".$category0."/", "/".$the_cat."/", $content);
}
return $content;
}
So, basically, I’m defining the categories with higher priority in the $cat_order array, and at the end search/replace the old category slug by the one with higher priority.
As you can see, it’s quite dirty… So if you figure out a cleanest way to do it, please let me know!
Just thought you should know, there’s now a plugin that seems to do this quite well: http://wordpress.org/extend/plugins/hikari-category-permalink
Thanks Ash, sounds very good !