Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save joshuadavidnelson/282614233c25071669f2502d77a7a8f9 to your computer and use it in GitHub Desktop.
Save joshuadavidnelson/282614233c25071669f2502d77a7a8f9 to your computer and use it in GitHub Desktop.
Using a Parent > Child category structure with duplicate child url slugs; for urls like `category/parent-1/child` and `category/parent-2/child`.
<?php
/**
* Plugin Name: Duplicated Child Term Url Slugs
* Description: Duplicate child term urls slugs in hierarchical taxonomies.
* Version: 0.1.0
* Author: joshuadnelson
* License: GPLv2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
*
* This is setup like a plugin, but can be included in a theme or as a class in a plugin.
*
* Using a Parent > Child category structure with duplicate child url slugs;
* for urls like `category/parent-1/child` and `category/parent-2/child`.
*
* Child term slugs must be unique per WordPress core requirements.
* In order to have duplicated child url slugs, the term slug must follow
* a pattern we can transform to tell WordPress how to route the url.
*
* Then apply a custom rewrite rule to match the pattern and filter the
* term link to remove the parent term slug from the child term url.
*
* Here the child slugs are structured like "{parent-slug}_{child-url-slug}"
* and the term link is shown as `category/{parent-slug}/{child-url-slug}`.
*
* Examples:
* - url `example.url/category/books/hobbit/`
* for the `books_hobbit` child term and `books` parent term
* - url `example.url/category/movies/hobbit/`
* for the `movies_hobbit` child term and `movies` parent term
*
* Notes:
* - Permalinks need to be "pretty" and need to be flushed after apply these changes.
* - Parent terms must be unique, child terms can be duplicated.
* - Terms cannot have a "_" in a slug other than for this purpose.
* - Child terms must contain the parent term slug, be careful if you change those on
* the parent term you will need to update the child term slugs as well.
* - Only works for hierarchical taxonomies, specifically the built-in 'category' taxonomy.
* You can modify the taxonomy name in the code to work with other taxonomies
* by changing the 'category' taxonomy name and the 'category_name' query var.
*/
/**
* Main class for the Duplicated Child Url Slugs plugin.
*
* @since 0.1.0
*/
class DuplicatedChildUrlSlugs {
/**
* The taxonomy name.
*
* @since 0.1.0
* @var string
*/
protected $taxonomy = 'category';
/**
* The taxonomy query var.
*
* @since 0.1.0
* @var string
*/
protected $query_var = 'category_name';
/**
* The separator used to separate parent and child term slugs.
*
* @since 0.1.0
* @var string
*/
protected $sep = '_';
/**
* Register the plugin
*
* @since 0.1.0
* @return void
*/
public function register() {
// Filter the term link to handle hierarchical taxonomy permalinks.
add_filter( 'term_link', array( $this, 'term_link' ), 10, 3 );
// Parse the query to handle hierarchical taxonomy permalinks.
add_action( 'parse_query', array( $this, 'parse_query' ) );
// Add custom rewrite rules for parent/child category urls.
add_action( 'init', array( $this, 'rewrite_rules' ), 100 );
}
/**
* Add custom rewrite rules for parent/child category 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.
*
* @since 0.1.0
* @return void
*/
function rewrite_rules() {
$tax_obj = get_taxonomy( $this->taxonomy );
if ( $tax_obj ) {
$tax_slug = $tax_obj->rewrite['slug'];
add_rewrite_rule(
$tax_obj->rewrite['slug'] . '/([^/]+)/([^/]+)/?',
'index.php?' . $tax_obj->query_var . '=$matches[1]_$matches[2]',
'top'
);
}
}
/**
* 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".
*
* @since 0.1.0
* @param string $link The term link
* @param WP_Term $term The term object
* @param string $taxonomy The taxonomy name
* @return string
*/
function term_link( $link, $term, $taxonomy ) {
if ( $taxonomy !== $this->taxonomy ) {
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;
}
/**
* 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"
*
* @since 0.1.0
* @param WP_Query $query The WP_Query instance (passed by reference)
* @return void
*/
function parse_query( $query ) {
// Check if we have the taxonomy query var set.
if ( isset( $query->query_vars[ $this->query_var ] ) ) {
// If we have a term slug with a separator, check if it's a child term
$slug = $query->query_vars[ $this->query_var ];
if ( strpos( $slug, $this->sep ) !== false ) {
// Separate the parent and child term slugs.
list( $parent, $child ) = explode( $this->sep, $slug, 2 );
// If the parent-child term does not exist, but the child term
// slug as-is is valid, then set the query var to the child term.
if ( ! term_exists( $slug, $this->taxonomy ) && term_exists( $child, $this->taxonomy ) ) {
$query->query_vars[ $this->query_var ] = $child;
}
}
}
}
}
$dup_url_slugs = new DuplicatedChildUrlSlugs();
$dup_url_slugs->register();
@chidinweke
Copy link

I attempted to use this plugin, but it didn't work. Is it a standalone plugin, or does it require another plugin to function properly?

@joshuadavidnelson
Copy link
Author

@chidinweke This is intended to work as a standalone plugin, but is very specific in it's use case. I've also saved it as a gist because it's not a fully supported project, just a proof of concept example.

It only works for core post categories where parent names are unique and child terms are the same, but within WordPress you have to structure the child slugs like "{parent-slug}_{child-url-slug}" and the term link is shown as category/{parent-slug}/{child-url-slug}.

After you install it, you'll likely need to refresh your permalinks as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment