0

I have a custom taxonomy called campaign and a custom post type called asset. For assets, I want to have the following permalink structure: mysite.com/<campaign_name>/<asset_name>. I have achieved this by the following code, but now if I go to any normal page with the url structure mysite.com/<pagename> it gives a 404. And when I comment out the rewrite slug part in the function for registering the custom post type, or add this instead ams/%campaign%, it works but that's not the URL structure I want for my custom post type.

Code for registering custom taxonomy:

...
'rewrite' => array(
    'slug' => '',
    'with_front' => true,
),
...

Code for registering custom post type:

...
rewrite' => array(
    'slug' => '%campaign%',
    'with_front' => true,
),
...

Functions for rewrite rules:

function ams_asset_add_rewrite_rules( $rules ) {
    global $post;
    if ($post->post_type == 'asset' ) {
        $new = array();
        $new['([^/]+)/(.+)/?$'] = 'index.php?asset=$matches[2]';
        $new['(.+)/?$'] = 'index.php?campaign=$matches[1]';
        return array_merge( $new, $rules );
    }
    return $rules;
}
add_filter( 'rewrite_rules_array', 'ams_asset_add_rewrite_rules' );

// Handle the '%campaign%' URL placeholder
function ams_asset_filter_post_type_link( $link, $post = 0 ) {
    if ( $post->post_type == 'asset' ) {
        $cats = wp_get_object_terms( $post->ID, 'campaign' );
        if ( $cats ) {
            $link = str_replace( '%campaign%', $cats[0]->slug, $link );
        }
    }
    return $link;
}
add_filter( 'post_type_link', 'ams_asset_filter_post_type_link', 10, 2 );

Edit: it's not a duplicate. I explained why that possible duplicate question doesn't solve my problem.

3
+100

Solution would be a little tricky but you're on the right path.

  1. You need to register taxonomy and post type with dynamic rewrite:
function wpse346452_cpt() {

    register_taxonomy( 'campaign', 'asset', array( 'label' => 'Campaing' ) );

    register_post_type( 'asset', array(
        'public' => true,
        'label' => 'Asset',
        'rewrite' => array(
            'slug' => '%campaign_name%',
        ),
    ) );

}
add_action( 'init', 'wpse346452_cpt' );
  1. You need to replace that dynamic rewrite so that it represents the term attached to it:
function wpse346452_permalink( $post_link, $post ) {
    if( is_object( $post ) && $post->post_type == 'asset' ) {
        $terms = wp_get_object_terms( $post->ID, 'campaign' );
        if( $terms ) {
            return str_replace( '%campaign_name%' , $terms[0]->slug , $post_link );
        }
    }
    return $post_link;
}
add_filter( 'post_type_link', 'wpse346452_permalink', 10, 2 );
  1. Now you get 404 because the rewrite rule is not defined yet. Each time you have a new term under campaign, you need a new rule. So let's add rewrite rules for each available terms dynamically:
function wpse346452_dynamic_rw_rules() {

    $terms = get_terms( array(
        'taxonomy' => 'campaign',
        'hide_empty' => false,
    ) );

    if( !empty( $terms ) ) {
        foreach( $terms as $term ) {
            add_rewrite_rule(
                '^' . $term->slug . '/(.*)/?$',
                'index.php?post_type=asset&name=$matches[1]',
                'top'
            );
        }
    }

}
add_action( 'init', 'wpse346452_dynamic_rw_rules' );

Now it will work as you needed. But the downside is, whenever you add new campaign, you need to go to permalink settings and save, thus flush rewrite rules. So let's automate that too:

function wpse346452_flush_rewrite( $term_id, $tt_id, $taxonomy = 'campaign' ) {
    if( $taxonomy === 'campaign' ) {
        $term = get_term_by( 'term_taxonomy_id', $tt_id );
        add_rewrite_rule(
            '^' . $term->slug . '/(.*)/?$',
            'index.php?post_type=asset&name=$matches[1]',
            'top'
        );
        if( !function_exists( 'flush_rewrite_rules' ) ) {
            require_once WPINC . '/rewrite.php';
        }
        flush_rewrite_rules();
    }
}
add_action( 'edit_term', 'wpse346452_flush_rewrite', 10, 3 );
add_action( 'create_campaign', 'wpse346452_flush_rewrite', 10, 3 );

Here we're hooking into create and edit term and if the taxonomy is campaign then we're firing flush_rewrite_rules() to refresh permalinks.

This is the only perfect method of doing this as far my knowledge. But there's a limitation for doing this. As there's no limit of adding terms, it can conflict with rewrite rules from other plugins. So need to be used carefully.

| improve this answer | |
  • This solved my problem, thank you very much! Gave you the bounty. Also, do you happen to know how I'd make the campaign URLs like this: domain.com/<campaign-name> instead of domain.com/campaign/<campaign-name>? – sadmansh Sep 11 '19 at 13:59
  • 1
    That's reserved for default post and page post types. But you can still use that by using 'rewrite' => array( 'slug' => '/' ). But you have to make sure that your permalink structure is something other than "Post name" so that's definitely a downside. – Sohan Zaman Sep 11 '19 at 16:44
0

My answer isn't directly applicable to your case as my experience is about doing funky stuff with CPT permalinks, but without taxonomies. But the end result was more or less the same as half of the site's content ended up showing 404 after starting to use custom permalink structures.

It took me ages to figure out what was going on and get the problem fixed. The problem was that WordPress had started thinking that some of the content were attachments instead of pages/posts/CPTS. To get the content showing again, I hooked a custom function to parse_request and basically brute forced the $wp->query_vars to be correct.

It might not be the most elegant or efficient solution, but it did the trick. I did try playing around with adding custom rewrite rules, but that got me nowhere as I couldn't wrap my head around the rewrite concepts.

Here's a simplified and stripped down version of the code I used to solve the 404 problem. The basic idea is that I just used what query_vars I had, did custom $wpdb query to get the right data, and then pushed that data to query_vars and removed unnecessary data from it.

add_action( 'parse_request',  'modify_parse_request' );
function modify_parse_request( $wp ) {
    if ( is_admin() || ! is_main_query() )  {
        return;
    }
    global $wpdb;

    // var_dump($wp->query_vars); // see whats really happening with your query

    if ( ! empty( $wp->query_vars['attachment'] ) ) {

        // $path = explode( '/', $wp->request ); // you might need to get the page name from request

        $sql = "
            SELECT ID, post_name, post_type
            FROM $wpdb->posts
            WHERE post_name=%s
        ";
        $post = $wpdb->get_results( $wpdb->prepare( $sql, $wp->query_vars['attachment'] ) ); // modify this

        // "brute force" the query variables to be correct
        if ( $post ) { // modify this
            $wp->query_vars['p'] = $post->ID;
            $wp->query_vars['post_type'] = $post->post_type;
            $wp->query_vars['name'] = $wp->query_vars['attachment'];
            unset( $wp->query_vars['attachment'] );
        }

    }

}

This is definetly not an copy-paste example, but just some food or thought. Maybe using this as a starting point, you can get your pages working again, if you use your custom taxonomy+cpt_post_name permalink structure.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.