I needed a solution for this and responded to a similar question here,
WordPress won't allow you to have duplicate term slugs in the same taxonomy, they need to be unique in the database. However, we can extend WordPress core to make the url structure work while maintaining the unique term slugs.
For example: category/books/hobbit
and category/movies/hobbit
urls where books_hobbit
and movies_hobbit
are the unique child terms slugs and their parent term slugs are books
and movies
, respectively.
First, the rewrite rule:
/**
* Add custom rewrite rules for parent/child term urls.
*
* Adds a rewrite rule that will match terms
* that have their slug prefixed with the parent term
* slug, separated by an underscore, but their url
* is just the child term slug.
*/
function prefix_custom_hierarchical_taxonomy_rewrite_rules() {
$tax_object = get_taxonomy( 'category' );
if ( $tax_object ) {
$tax_slug = $tax_object->rewrite['slug'];
add_rewrite_rule(
$tax_object->rewrite['slug'] . '/([^/]+)/([^/]+)/?',
'index.php?' . $tax_object->query_var . '=$matches[1]_$matches[2]',
'top'
);
}
}
add_action( 'init', 'prefix_custom_hierarchical_taxonomy_rewrite_rules', 100 );
Then filter the term_link
such that you remove the {parent-slug}_
part of the slug in the final child term's link:
/**
* Filter the term link to handle hierarchical taxonomy permalinks.
*
* This will filter the term link to remove the parent term slug
* from the child term url, so that the url is just the child term slug,
* where the child term name is "parent_child" and the parent term name is "parent".
*
* @param string $link The term link
* @param WP_Term $term The term object
* @param string $taxonomy The taxonomy name
* @return string
*/
function prefix_custom_term_link( $link, $term, $taxonomy ) {
if ( $taxonomy !== 'category' ) {
return $link;
}
// Get the parent term
$parent_term = get_term( $term->parent, $taxonomy );
if ( $parent_term && ! is_wp_error( $parent_term ) ) {
// Remove the "parent_" prefix from the child term slug
// to generate a url that looks like /parent/child/
$link = str_replace( '/' . $term->slug, '/' . str_replace ( $parent_term->slug . '_', '', $term->slug ), $link );
}
return $link;
}
add_filter( 'term_link', 'prefix_custom_term_link', 10, 3 );
You may also want to filter the parse_query
to check for any child terms that don't follow this structure. This avoids having to set all your child category slugs to the parent_child
pattern, you can just apply it to those that have a "duplicated" url slug.
/**
* Parse the query to handle hierarchical taxonomy permalinks.
*
* This filter the query to check for terms that are child terms
* yet do not have the parent term in the slug, thus the parent/child
* structure should literally look for a "child" slug, not "parent_child"
*
* @param WP_Query $query The WP_Query instance (passed by reference)
* @return void
*/
function prefix_custom_parse_query($query) {
if ( isset( $query->query_vars['category_name'] ) ) {
$slug = $query->query_vars['category_name'];
if ( strpos( $slug, '_' ) !== false ) {
list( $parent, $child ) = explode( '_', $slug, 2 );
if ( ! term_exists( $slug, 'category' ) && term_exists( $child, 'category' ) ) {
$query->query_vars['category_name'] = $child;
}
}
}
}
add_action( 'parse_query', 'prefix_custom_parse_query' );
I've put this into a gist here.
Notes:
- This only works with hierarchical taxonomies, in this example we're using the built-in
category
taxonomy
- Update the
prefix_
naming to something unique to your theme or plugin.
- This example uses a custom term slug for your child terms that follows a pattern like
{parent-slug}_{child-slug}
. You could change that to another separating character, but be careful to use a dash -
as that is the default used by core when names are generated (spaces turn to dashes).
- This requires the "pretty" permalinks where
/%postname%/
is set in the Settings > Permalinks.