Wordpress - Custom Post type & Taxonomy URL structure
After forever, I figured out an answer!
First: we register the custom post type & custom taxonomy:
add_action( 'init', 'register_sps_products_post_type' );
function register_sps_products_post_type() {
register_post_type( 'sps-product',
array(
'labels' => array(
'name' => 'Products',
'menu_name' => 'Product Manager',
'singular_name' => 'Product',
'all_items' => 'All Products'
),
'public' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => true,
'show_in_nav_menus' => true,
'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'comments', 'post-formats', 'revisions' ),
'hierarchical' => false,
'has_archive' => 'products',
'taxonomies' => array('product-category'),
'rewrite' => array( 'slug' => 'products/%product_category%', 'hierarchical' => true, 'with_front' => false )
)
);
register_taxonomy( 'product-category', array( 'sps-product' ),
array(
'labels' => array(
'name' => 'Product Categories',
'menu_name' => 'Product Categories',
'singular_name' => 'Product Category',
'all_items' => 'All Categories'
),
'public' => true,
'hierarchical' => true,
'show_ui' => true,
'rewrite' => array( 'slug' => 'products', 'hierarchical' => true, 'with_front' => false ),
)
);
}
Next, we add a new rewrite rule so Wordpress knows how to interpret our new permalink structure:
add_action( 'generate_rewrite_rules', 'register_product_rewrite_rules' );
function register_product_rewrite_rules( $wp_rewrite ) {
$new_rules = array(
'products/([^/]+)/?$' => 'index.php?product-category=' . $wp_rewrite->preg_index( 1 ), // 'products/any-character/'
'products/([^/]+)/([^/]+)/?$' => 'index.php?post_type=sps-product&product-category=' . $wp_rewrite->preg_index( 1 ) . '&sps-product=' . $wp_rewrite->preg_index( 2 ), // 'products/any-character/post-slug/'
'products/([^/]+)/([^/]+)/page/(\d{1,})/?$' => 'index.php?post_type=sps-product&product-category=' . $wp_rewrite->preg_index( 1 ) . '&paged=' . $wp_rewrite->preg_index( 3 ), // match paginated results for a sub-category archive
'products/([^/]+)/([^/]+)/([^/]+)/?$' => 'index.php?post_type=sps-product&product-category=' . $wp_rewrite->preg_index( 2 ) . '&sps-product=' . $wp_rewrite->preg_index( 3 ), // 'products/any-character/sub-category/post-slug/'
'products/([^/]+)/([^/]+)/([^/]+)/([^/]+)/?$' => 'index.php?post_type=sps-product&product-category=' . $wp_rewrite->preg_index( 3 ) . '&sps-product=' . $wp_rewrite->preg_index( 4 ), // 'products/any-character/sub-category/sub-sub-category/post-slug/'
);
$wp_rewrite->rules = $new_rules + $wp_rewrite->rules;
}
The next function on the other hand fixes the issue with the sub-categories. The issue itself consists in the fact, that when you try to load a page with URL faq/category/child-category/, WordPress would try to load a post with slug “child-category” instead of the sub-category with slug “child-category”.
// A hacky way of adding support for flexible custom permalinks
// There is one case in which the rewrite rules from register_kb_rewrite_rules() fail:
// When you visit the archive page for a child section(for example: http://example.com/faq/category/child-category)
// The deal is that in this situation, the URL is parsed as a Knowledgebase post with slug "child-category" from the "category" section
function fix_product_subcategory_query($query) {
if ( isset( $query['post_type'] ) && 'sps-product' == $query['post_type'] ) {
if ( isset( $query['sps-product'] ) && $query['sps-product'] && isset( $query['product-category'] ) && $query['product-category'] ) {
$query_old = $query;
// Check if this is a paginated result(like search results)
if ( 'page' == $query['product-category'] ) {
$query['paged'] = $query['name'];
unset( $query['product-category'], $query['name'], $query['sps-product'] );
}
// Make it easier on the DB
$query['fields'] = 'ids';
$query['posts_per_page'] = 1;
// See if we have results or not
$_query = new WP_Query( $query );
if ( ! $_query->posts ) {
$query = array( 'product-category' => $query['sps-product'] );
if ( isset( $query_old['product-category'] ) && 'page' == $query_old['product-category'] ) {
$query['paged'] = $query_old['name'];
}
}
}
}
return $query;
}
add_filter( 'request', 'fix_product_subcategory_query', 10 );
This function lets WordPress know how to handle %product_category% in your custom post type rewrite slug structure:
function filter_post_type_link($link, $post)
{
if ($post->post_type != 'sps-product')
return $link;
if ($cats = get_the_terms($post->ID, 'product-category'))
{
$link = str_replace('%product_category%', get_taxonomy_parents(array_pop($cats)->term_id, 'product-category', false, '/', true), $link); // see custom function defined below\
$link = str_replace('//', '/', $link);
$link = str_replace('http:/', 'http://', $link);
}
return $link;
}
add_filter('post_type_link', 'filter_post_type_link', 10, 2);
A custom function based off of get_category_parents
. It gets the taxonomy's parents:
// my own function to do what get_category_parents does for other taxonomies
function get_taxonomy_parents($id, $taxonomy, $link = false, $separator = '/', $nicename = false, $visited = array()) {
$chain = '';
$parent = &get_term($id, $taxonomy);
if (is_wp_error($parent)) {
return $parent;
}
if ($nicename)
$name = $parent -> slug;
else
$name = $parent -> name;
if ($parent -> parent && ($parent -> parent != $parent -> term_id) && !in_array($parent -> parent, $visited)) {
$visited[] = $parent -> parent;
$chain .= get_taxonomy_parents($parent -> parent, $taxonomy, $link, $separator, $nicename, $visited);
}
if ($link) {
// nothing, can't get this working :(
} else
$chain .= $name . $separator;
return $chain;
}
Sources:
- Add Taxonomy in Custom Permalink Structure
- Custom Permalinks Hierarchical Taxonomies