Say you have a custom taxonomy called “sport” and you wanted to inject “football” into the permalink of posts that have the “football” term assigned to it. How would you go about doing that? Well below is a helper class that will make it easy to accomplish exactly that.
As a reminder, here’s how you go about registering a custom taxonomy using the register_taxonomy()
function:
[code language=”php”]add_action( ‘init’, ‘register_sport_taxonomy’ );
function register_sport_taxonomy() {
register_taxonomy( ‘sport’, ‘post’, array(
‘labels’ => array(
‘name’ => ‘Sports’,
),
‘hierarchical’ => true, // Makes for a better selection box on write screen
) );
}[/code]
And now, for my helper class:
(feel free to highlight and then copy/paste this code into your favorite text editor to make it easier to read)
[code language=”php”]/**
* A helper class for registering and handling a custom rewrite tag for a custom taxonomy.
*
* @version 1.1.0
*/
class Add_Taxonomy_To_Post_Permalinks {
/**
* Stores the taxonomy slug that this class will be handling. Don’t edit this.
*
* @since 1.0.0
* @var string
*/
public $taxonomy;
/**
* Stores the rewrite tag complete with percentage signs. Don’t edit this.
*
* @since 1.0.0
* @var string
*/
public $rewrite_tag;
/**
* Initializes the class by calling Add_Taxonomy_To_Post_Permalinks::register()
* as well as registering a filter that runs in get_permalink().
*
* @since 1.0.0
*
* @param string $taxonomy A taxonomy slug. Use the same one that you used with register_taxonomy().
* @return array $optional_args Optional configuration parameters. See Add_Taxonomy_To_Post_Permalinks::register().
*/
function __construct( $taxonomy, $optional_args = array() ) {
if ( ! $this->register( $taxonomy, $optional_args ) )
return;
// Normal posts
add_filter( ‘post_link’, array( &$this, ‘filter_post_link’ ), 10, 2 );
// Custom post types
add_filter( ‘post_type_link’, array( &$this, ‘filter_post_link’ ), 10, 2 );
}
/**
* Registers the rewrite tag using add_rewrite_tag().
*
* Can accept an array of optional parameters:
*
* * tagname: The rewrite tag to use (no percentage signs). Defaults to the taxonomy slug.
* * regex: What regex to use to validate the value of the tag. Defaults to anything but a forward slash.
*
* @since 1.0.0
*
* @param string $taxonomy A taxonomy slug. Use the same one that you used with register_taxonomy().
* @return array $optional_args Optional configuration parameters. See function description.
*/
public function register( $taxonomy, $optional_args = array() ) {
if ( ! taxonomy_exists( $taxonomy ) )
return false;
$this->taxonomy = $taxonomy;
$this->rewrite_tag = ( ! empty( $optional_args[‘tagname’] ) ) ? $optional_args[‘tagname’] : $this->taxonomy;
$this->rewrite_tag = ‘%’ . $this->rewrite_tag . ‘%’;
$rewrite_tag_regex = ( ! empty( $optional_args[‘regex’] ) ) ? $optional_args[‘regex’] : ‘([^/]+)’;
// See http://codex.wordpress.org/Rewrite_API/add_rewrite_tag
add_rewrite_tag( $this->rewrite_tag, $rewrite_tag_regex );
return true;
}
/**
* Filters a post permalink to replace the tag placeholder with the first
* used term from the taxonomy in question.
*
* @since 1.0.0
*
* @param string $permalink The existing permalink URL.
* @return object $post The post object that this permalink belongs to.
*/
public function filter_post_link( $permalink, $post ) {
// Abort early if the placeholder rewrite tag isn’t in the generated URL
if ( false === strpos( $permalink, $this->rewrite_tag ) )
return $permalink;
// Get the custom taxonomy terms in use by this post
$terms = get_the_terms( $post->ID, $this->taxonomy );
// If no terms are assigned to this post, use the taxonomy slug instead (can’t leave the placeholder there)
if ( empty( $terms ) ) {
$permalink = str_replace( $this->rewrite_tag, $this->taxonomy, $permalink );
}
// Replace the placeholder rewrite tag with the first term’s slug
else {
$first_term = array_shift( $terms );
$permalink = str_replace( $this->rewrite_tag, $first_term->slug, $permalink );
}
return $permalink;
}
}[/code]
To use the class, simply initiate a new instance of it and pass the taxonomy slug to it while doing so:
[code language=”php” highlight=”11,12″]add_action( ‘init’, ‘register_sport_taxonomy’ );
function register_sport_taxonomy() {
register_taxonomy( ‘sport’, ‘post’, array(
‘labels’ => array(
‘name’ => ‘Sports’,
),
‘hierarchical’ => true, // Makes for a better selection box on write screen
) );
$sport_taxonomy_permalinks
= new Add_Taxonomy_To_Post_Permalinks( ‘sport’ );
}[/code]
Then go to Settings → Permalinks and add %taxonomy%
to your structure, such as /%sport%/%year%/%monthnum%/%day%/%postname%/
. It will then be replaced by the first term (sorted by term ID) of your custom taxonomy.
More complicated solutions are of course possible, such as picking which of multiple terms you want injected into the URL or multiple terms in the URL, but my class should give you a good starting point to work off of. 馃檪
Hello,
I was wandering what to do if one had the following:
路 CPT: products
路 Taxonomy: product_types
路 Term: cellphones
路 Child-term: apple
路 custom post: iphone-4
…and trying accomplish this;
[ / products / cellphones / apple / iphone-4 ]
…as in;
[ / CPT / term / child-term(s) / postname ]
The end result would be something like;
/products 禄 You view every custom post
/products/cellphones 禄 You view every post related to the term cellphones
/products/cellphones/samsung/ 禄 You view every post related to samsung
Thank you,
//DRSK
@DRSK – I too would like to know how to achieve this.
That should be doable — just expand upon my code and allow for
/
to be matched by the regex for the custom taxonomy. My example is using([^/]+)
which allows everything but slashes.I would not separate out child terms as you have done though — just use a single rewrite tag and have the
post_link
filter replace the tag with a string that has some slashes in it.Setting up archives is a completely separate bit of code though but also doable. At the worst you could just manually set up the rules:
http://codex.wordpress.org/Custom_Queries#Permalinks_for_Custom_Archives
Thanks for a very useful snippet. Is there a way to restrict taxonomy “injections” to specific post types?
For example, I want posts in my post type ‘movies’ to have URLs containing taxonomy terms from the ‘genre’ taxonomy, and posts in my post type ‘software’ to have URLs using the ‘language’ taxonomy.
I would use a shared rewrite tag, such as
%taxonomy%
and then in yourpost_link
callback function (filter_post_link()
in my example), check what the$post->post_type
is and vary what you inject into the URL based on that post type.Thanks Alex for this great & simple helper class.
One little thing, you should add [code]add_filter( ‘post_type_link’, array( &$this, ‘filter_post_link’ ), 10, 2 );[/code] after line 37 of your class to make it work with Custom Post Type as Well as normal Post.
Good call.
Just a note, in order to affect any post type other than posts you would need to modify the permastruct, which isn’t obvious here.
Can we use this function to change the permalink structure for other values, Like i want to change the monthnumber to monthname . can it be done ?
Hi,
Thanks for this tutorial but helper Class doesn’t work for me. :/
Hello,
Amazing Code, Been looking for this for a long time and finally it WORKS!
Two Questions:
1 Is there a way to limit this code to only work on certain custom post types.
2. Keeping 1 in mind, if there is no taxonomy assigned is there a way to not add the /tax-term/.
for example, we have a custom post type called article and a taxonomy called topic.
– if there is a topic i get the following permalink
/fast-facts/awesome-days-straight-ahead/
(fast-fact is the topic and than awesome-days-straight-ahead is the slug (dont want to change this)
– if there is no topic i get the following permalink
/topic/any-messianic-visions-in-your-neighborhood/
so if there is no topic assigned to the post i want it to be just /slug instead of /topic/slug
How can i accomplish this.
Thanks
-Yosef
My example code only works on the custom post types you enable it for. You can also set custom permalink structures per post type as a part of registering the post type.
As for 2, you can probably replace this:
$permalink = str_replace( $this->rewrite_tag, $this->taxonomy, $permalink );
With this:
$permalink = str_replace( $this->rewrite_tag . '/', '', $permalink );
Untested though, it may not work.
This most is more meant to give you a direction to go on rather than give you a final solution to all situations.
Alex, Tried your code and it gives a 404 if there is no topic assigned. Any idea how to fix this
Pingback: A (Mostly) Complete Guide to the WordPress Rewrite API - PMG - Advertising Agency