Permalinks with custom taxonomies and regular categories

Assuming 2 posts:

  • “News about Stephen King” | Category: “News” | “Author”: “Stephen King”
  • “Review for Under the Dome” | Category: “Reviews” | “Book”: “Under the Dome”

where “Book” and “Author” are custom taxonomy.

If I understand well you want that the link for first will be:

  • /author/stephen-king/news/news-about-stephen-king

and the url for the second:

  • /book/under-the-dome/reviews/review-for-under-the-dome

If so, using WordPress standard rewrite API this is a very tricky task. However, for cases like this, I wrote a plugin, Clever Rules, you can find it here.

Docs are here.

After you have downloaded, put in your plugin folder and activate it.

Now you have to register your rule. To do this, you need to write a simple plugin (or add code to an existing plugin).

Create a new php file an in it put something like:

<?php
/**
 * Plugin Name: WPSE #125186 Tricky Rule
 *
 */

add_action('plugins_loaded', 'register_my_tricky_rule'); 

function register_my_tricky_rule() {
if ( ! function_exists('register_clever_rule') ) return;
  $args = array(
    'id' => 'my_custom_rule',
    'route' => '/%s/%s/%s/%s',
    'query' => array('name'=>'[3]')
  );
  register_clever_rule( $args );
}

Save it in your plugin folder and activate it.

At this moment the links you want just works, but it will fuck up any other WordPress link composed by 4 “pieces” e.g. a-parent-tax/a-child/a-granchild/something.

To avoid this, you can run the rule only if the pieces are the right ones. This can be done using the filter hook skip_clever_rule.

In the plugin file, add something like:

add_filter('skip_clever_rule', 'skip_my_tricky_rule', 10, 3);

function skip_my_tricky_rule( $skip, $rule, $pieces ) {
  if ( $rule['id'] == 'my_custom_rule' ) {
    $custom_taxes = get_taxonomies(
      array('public'=> true,'_builtin' => false, 'object_type'=> array('post') )
    );
    if ( ! in_array( $pieces[0], $custom_taxes ) ) return true;
    // one of or both next two lines can be removed for performance reasons
    if ( ! get_terms($pieces[0], array('fields'=>'ids', 'slug'=>$pieces[1]))) return true;
    if ( ! get_terms('category', array('fields'=>'ids', 'slug'=>$pieces[2]))) return true;
  }
}

Thanks to this filter you run the custom rule only if the first 3 pieces of the url are valid:

  1. if the first is a valid custom taxonomyname, e.g. ‘author’
  2. if the second is a valid term of this taxonomy, e.g. ‘stephen-king’
  3. if the third is a vali category term, e.g. ‘news’

This 3 checks are done one by one (see the 3 if in the function). However this cost in term of performace, so probably you can remove the last 2 checks, but in this way any url like:

/author/anything-here/anything-here/news-about-stephen-king

will open the news-about-stephen-king post.

For a compromise between accuracy and performance you can remove one of the 2 checks and leave the other.

If you put the two code blocks in the new plugin file and save, your links just works, however, at this moment, you have to manually write the urls in the address bar…

Next step is to filter the post link creation to create the url in the way you want. add the following code in the same plugin file:

add_filter( 'post_link', 'my_tricky_post_link', 10, 2 );

function my_tricky_post_link( $url, $post ) {
  if ( $post->post_type == 'post' ) {
    $custom_taxes = get_taxonomies(
      array('public'=>true, '_builtin'=>false, 'object_type'=>array('post'))
    );
    if ( empty($custom_taxes) ) return $url;
    $newurl="";
    while( empty($newurl) && ! empty($custom_taxes) ) {
      $tax = array_shift( $custom_taxes );
      $terms = get_the_terms( $post->ID, $tax );
      if ( ! empty($terms) ) {
        $term = array_shift( $terms );
        $newurl = $term->taxonomy ."https://wordpress.stackexchange.com/" . $term->slug;
      }
    }
    if ( empty($newurl) ) return $url;
    $categories = get_the_category( $post->ID );
    if ( ! empty($categories) ) {
      $cat = array_shift( $categories );
      $newurl .= "https://wordpress.stackexchange.com/" . $cat->slug;
      $url = home_url( $newurl . "https://wordpress.stackexchange.com/" . $post->post_name );
    }
  }
  return $url;
}

That’s all. note that this will work with any custom taxonomy, without changing a line of code.


Full plugin code can be downloaded here.