Support » Developing with WordPress » URL generation issues w/ Custom Taxonomy on CPT

  • Hey everyone. So I’m struggling with a legacy theme I’m cleaning up some of the URL hierarchy on. And I’m hitting some dead ends and hoping that someone can help aim me in a better direction.

    The Problem Statement
    So I’ve got a CPT called ‘tours’. And I have a hierarchical custom taxonomy called destination. The idea is to end up with URLS that look like this:
    /tours/europe/western-europe/france/14-day-french-extravaganza

    Where the /europe/western-europe/france part is the custom taxonomy.

    It ‘feels’ like this should be simple and just work. Just setup the CPT and taxonomy, tell the tax that it’s hierarchical, and tell the CPT that it should use that taxonomy for an archive setup. But of course that simplicity doesn’t seem to work.

    Where I am
    Currently I have it working that if you go to:
    /tours/europe … you get all tours in europe

    And that the canonical URL of the tour in question is in fact:
    /tours/europe/western-europe/france/14-day-french-extravaganza

    The problem is everything in the middle.. If you go to /tours/europe/western-europe for example. It errors/404’s. Because my custom rewrites are telling it that obviously western-europe must be a post, not a tax. And that’s where I’m kinda stuck.

    So I’m hoping someone can point out either what I need to do (complex), or what the simple issue is that I’m running into.

    Code
    Here’s some sample code showing what I have right now:

    The Tax:

    register_taxonomy(
     		'destination', 'tours',
     		[
     			'label' => __( 'Destination' ),
     			'rewrite' => [
    				'slug' => 'tours',
    				'hierachical' => true,
    				'with_front' => false, 
    			],
    			'public' => true,
     			'hierarchical' => true,
     			'show_in_nav_menus' => true,
     			'show_in_rest' => true,
     			'description' => 'All destinations that a tour goes to'
     		]
     	);

    The CPT:

    register_post_type('tours', array (
      'labels' => 
      array (
        'name' => 'Tours',
        'singular_name' => 'Tour',
        'add_new_item' => 'Add New Tour',
        'edit_item' => 'Edit Tour',
        'new_item' => 'New Tour',
        'view_item' => 'View Tour',
        'view_items' => 'View Tours',
      ),
      'public' => true,
      'exclude_from_search' => true,
      'has_archive' => 'tours',
      'capability_type' => 'page',
      'rewrite' => 
      array (
        'slug' => 'tours/%destination_tax%',
        'with_front' => false,
      ),
      'supports' => 
      array (
        'title',
        'editor',
        'excerpt',
        'trackbacks',
        'custom-fields',
        'revisions',
        'thumbnail',
        'author',
        'page-attributes',
      ),
      'show_in_rest' => true,
      'hierarchical' => false,
      'taxonomies' => 
      array (
        'destination',
      ),
      'menu_icon' => 'dashicons-location-alt',
    )

    Custom post links setup, to build out the full canonical URLs:

    function custom_post_links( string $post_link, \WP_Post $post, bool $leavename ): string
    {
    	if ($post->post_type === 'tours') {
                    // Using yoast primary term feature
    		$termID = \yoast_get_primary_term_id('destination', $post);
    		$destinationURL = wpterm_build_hierarchy($termID);
    		return str_replace('%destination_tax%', $destinationURL, $post_link);
    	}
    }
    add_filter( 'post_type_link', 'custom_post_links', 10, 3 );
    function wpterm_build_hierarchy($termID, $result = "") {
    	if ($termID) {
    		$term = \get_term($termID);
    		if ($term && !is_wp_error($term)) {
    			return wpterm_build_hierarchy($term->parent, $term->slug . '/' . $result);
    		}
    	}
    	return trim($result, '/');
    }

    And the custom rewrite rule for the full URL:

    function custom_rewrite_rules() {
    	// Handle rewriting our tour pages w/ taxonomy:
    	$terms = get_terms([
    		'taxonomy' => 'destination',
    		'hide_empty' => true,
    	]);
    
    	if (!empty($terms) && !is_wp_error($terms)) {
    		$slugs = [];
    		foreach ($terms as $term) {
    			$slugs[] = $term->slug;
    		}
    		$sluggos = implode('|', $slugs);
    		add_rewrite_rule(
    			"^tours/(?:(?:{$sluggos})/)*([^/]*)$",
    			'index.php?post_type=tours&name=$matches[1]',
    			'bottom');
    	}
    }
    add_action('init', 'custom_rewrite_rules');
Viewing 3 replies - 1 through 3 (of 3 total)
  • Thread Starter EliW

    (@eliw)

    So I should add. That really the catch-22 seems to be, that since I can’t figure out how to just tell WP to ‘make this happen’. Is that I have to use the custom rewrite-rules. And in the end the custom rules can’t really know if that ‘final item’ is a CPT post, or a taxonomy item. Since at the rewrite level I’m only looking at the raw text, I can’t introspect (or shouldn’t?) what that item that was found actually is.

    NOTE: I believe that ‘a’ solution is to use a different starting stub for these … right? So if I had:
    /destination/europe/western-europe
    vs /tours/europe/western-europe/france/14-day-extravaganza
    then I could have two separate rewrite rules. But that kinda defeats the purpose here of having a logical, hierarchical, structure.

    Moderator bcworkz

    (@bcworkz)

    Based on the way WP interprets URLs, using a CPT base (tours) without a CPT slug is illogical šŸ™‚ A “destination” base wouldn’t expect there to be a CPT slug, so it’s thus logical. You cannot accomplish what you want with rewrite rules alone. What may work is to alter how WP assigns query vars from the rewrite parameters. Take the final term, which would typically be the CPT slug. See if such a post exists. If it does, all is well. If it is not, assume the entire thing after /tours/ is all a taxonomy hierarchy and assign the entire thing to to the appropriate query var. There may be other query vars to alter in order to get the request to properly confirm to a taxonomy term archive.

    Thread Starter EliW

    (@eliw)

    Thanks bcworkz … Looks like I have it working now under a similar concept to what you proposed. Just doing some extra fancy rewrite rules to detect if it appears to be ‘only terms’ in the URL, and if so, rewrite to a tax page. Otherwise assume it’s a tour, and rewrite accordingly. At the moment, looks to work.

Viewing 3 replies - 1 through 3 (of 3 total)
  • You must be logged in to reply to this topic.