-
-
Save joshuadavidnelson/282614233c25071669f2502d77a7a8f9 to your computer and use it in GitHub Desktop.
<?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(); |
Hello, first of all, thank you for your plugin. I'm trying to do it for woocommerce product categories but I couldn't do it. Can you help me? Example of the URL structure I want to use
curtain/rooms/living-room/
wall-murals/rooms/living-room/
Have a nice day
Hey @sergenadiyaman. You're example needs to account for two modifications: a custom taxonomy and another level of duplication in the url slug - nesting 3-terms deep instead of 2-terms deep.
Custom taxonomy: product_cat
This example is for the built-in category
taxonomy, you'll need to make some changes to use this on custom taxonomies.
I believe WooCommerce "product categories" are registered as product_cat
, so you'll first want set $taxonomy = 'product_cat'
and also $query_var = 'product_cat'
(I'm not 100% sure on the WooCommerce naming for this, but the taxonomy slug is usually the default query_var).
Additional level to the url pattern
This is more complicated. You'll also need to update this to support 3-levels of nested permalink structure, this gist adds support for two levels (/parent/child/
) but your example has three (/grandparent/parent/child/
), which means the rewrite rule needs to look at three different parts of the link and you'll need a consistent term slug pattern to match to the correct grandparent, parent, and child terms.
For instance you may need to name each of those terms as:
curtain
+curtain_rooms
+curtain_rooms_living-room
to support a url like/curtain/rooms/living-room/
wall-murals
+wall-murals_rooms
+wall-murals_rooms_living-room
to support a url likewall-murals/rooms/living-room/
...and then parse that permalink to match the terms accordingly, some like this (warning I have not tested this):
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]_$matches[3]',
'top'
);
}
}
You'll also need to update the logic in the term_link
filter to look for a grandparent term & update the url. Lastly, the parse_query
would need to be updated as well, to accommodate terms not matching these patterns. Best of luck!
I'm sorry, I'm not a software developer. I'm just trying to make my website more efficient and I discovered your plugin. It works for post types, but I couldn't do it for Woocommerce product categories. I have no idea which taxamony to change or how to update term_link. I would appreciate it if you could write more clearly. Or it would be great if you could make a new plugin like the one you made. Thank you for your help
To get this gist working with WooCommerce you should only need to change two lines:
- Line 55 from
protected $taxonomy = 'category';
toprotected $taxonomy = 'product_cat';
- Line 63 from
protected $query_var = 'category_name';
toprotected $query_var = 'product_cat';
.
However, that will still only work for a 2-level permalink structure like rooms\living-rooms
. Going to a 3-level permalink structure like curtain/rooms/living-room/
is outside of the scope of this plugin as it's currently written.
thanks for your help
Thank you for this. This works for categories but doesn't seem to do anything for underlying post permalinks.
- example.url/top1/sub/
- example.url/top2/sub/
- example.url/top1/top1_sub/post
Am I missing something there? Can it be modified to carry over to underlying posts?
Hey @johnflufin! This is really targeted at custom category permalink bases, what you're talking about is the category + post permalink base.
I think in that case you'd need to add another rewrite rule or two to cover conditions where you're using /cat/subcat/post
structure, to tell WP what you expect the queried object for that url to be (the post id). You would update the rewrite_rules
method in this example to include a rewrite condition and add some logic in the parse_query
to account for it.
That could get really complex if you support categories on a hierarchal post type like pages, where you can have nested pages page/subpage
in cat/subcat/page/subpage
etc.
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?
@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.
For a custom taxonomy, set the
$taxonomy
var as your taxonomy slug and$query_var
with your taxonomy's query variable (seeregister_taxonomy
)