Permalink for CPT with taxonomy

[Edited]

I changed the purpose of my site into an language learning site.

Now the CPT is setup as following:

{ Language } – { Subject }

  • English speaking (slug: english-speaking )
  • English writing (english-writing )
  • English reading ( english-speaking )
  • English writing (english-writing)

Single CPT setup

Because each CPT might have different taxonomy… so it is preferred this would be taking into consideration of the code.

Say: CPT “English Speaking” has a custom taxonomy of “Speaking Task” (speaking-task) with terms “task-1”, “task-2”, “task-3” … . But: CPT “English Writing” has no taxonomy at all.

Desired URL for single CPT post item:

With custom taxonomy terms:

  • www.domain.com/{language}/{subject}/{ term-slug }/{custom-question-id}/{post-name}
  • ex. www.domain.com/english/speaking/speaking-1/32/blah-blah-blah

Without custom taxonomy terms:

  • www.domain.com/{language}/{subject}/{custom-question-id}/{post-name}
  • ex. www.domain.com/english/writing/32/blah-blah-blah

The URL rewrite part for Single CPT is not a problem for me now thanks to your teaching and genius code provided in the new answer

What I am struggling with is the rewrite for the CPT Archive page below:


CPT Archive setup

I am trying to create a custom CPT Archive template with the following setup for URL:

  • www.domain.com/ { language } / { subject }
    ex. www.domain.com/english/speaking

  • www.domain.com/ { language } / { subject } / { terms-slug }

  • ex1. www.domain.com/english/speaking (instead of listing all posts, I wish to make it default to list out to “speaking-1”)
  • ex2. www.domain.com/english/speaking/speaking-1 (only list out post under “speaking-1” )
  • ex3. www.domain.com/english/speaking/speaking-2 (only list out post under “speaking-2” )

———————————————————————————————————

In [archive-english-speaking.php] , I have the following code:

// use add_rewrite_rule in funcitons; query_vars "task" is terms of speaking-task taxonomy
global $wp_query;
$task_no = $wp_query->query_vars['task'];


if (preg_match("/[1-6]/", $task_no)) {
  $task = ( 'speaking-' . $task_no );
} else {
  $task_no = ( '1' );
  $task = ( 'speaking-' . $task_no );
}

$posts_per_page = 10;


$the_query = new WP_Query( array( 
  'post_type' => 'english-speaking',
  'taxonomy' => 'speaking-task',
  'term' => $task,
  /*'paged' => $paged,
  'posts_per_page' => $posts_per_page,*/
  'orderby'   => 'meta_value_num',
  'meta_key' => 'wpcf-question-id',
  'order'   => 'ASC', /* ASC/DESC */
   ) );

———————————————————————————————————

In [functions.php] , I have the following code:

function custom_rewrite_tag() {
    //taxonomy speaking-task =?
    add_rewrite_tag('%task%', '([^/]*)');
}
add_action('init', 'custom_rewrite_tag', 10, 0);



// Custom Question ID from custom field
function cqid() {
    $cqid = get_post_meta( get_the_ID(), 'wpcf-question-id', true );
    return $cqid;
}


function my_questions_cpt_list() {
    return [
    // Format: POST_TYPE   => QUESTION_SUBJECT_SLUG
        'english-listening' => 'listening',
        'english-speaking' => 'speaking',
        'english-reading' => 'reading',
        'english-writing' => 'writing',
        // Add more items as needed, or if and when you register another CPT
        // for questions.
    ];
}


add_action( 'init', 'my_questions_cpt_add_rewrite_rules' );
function my_questions_cpt_add_rewrite_rules() {


// Archive Page 
add_rewrite_rule( '^english/speaking/?$','index.php?post_type=english-speaking&taxonomy=speaking-task&task=$matches[1]', 'top' );
add_rewrite_rule( '^english/speaking/([^/]*)/?$','index.php?post_type=english-speaking&taxonomy=speaking-task&task=$matches[1]', 'top' );


//
$cpt_list = my_questions_cpt_list();
foreach ( $cpt_list as $post_type => $q_subject ) {
    if ( empty( $post_type ) ) {
        continue;
    }

    if ( ! $s = preg_quote( $q_subject ) ) {
        continue;
    }


    add_rewrite_rule('^english/(' . $s . ')/(\d+)/(\d+)/([^/]+)/?','index.php?' . $post_type . '=$matches[4]','top');
}
}



add_filter( 'post_type_link', 'my_questions_cpt_custom_permalink', 10, 2 );
function my_questions_cpt_custom_permalink( $permalink, $post ) {
    $taxonomy = 'speaking-task';
    $cpt_list = my_questions_cpt_list();

if ( false === strpos( $permalink, '?' ) &&
$post && isset( $cpt_list[ $post->post_type ] ) ) {
    $cats = get_the_terms( $post, $taxonomy );
    if ( $cats && ! is_wp_error( $cats ) ) {
        // If assigned to multiple tasks (or categories), then we use just
        // the first task/term.

        $cat_id = $cats[0]->slug; //term_id
        $task = $cat_id[strlen($cat_id)-1];
        $cqid = cqid();

        $s = $cpt_list[ $post->post_type ];
        $permalink = home_url( 'english/' . $s . "https://wordpress.stackexchange.com/" . $task . "https://wordpress.stackexchange.com/" . $cqid . "https://wordpress.stackexchange.com/" . $post->post_name . "https://wordpress.stackexchange.com/" );
        $permalink = user_trailingslashit( $permalink, 'single' );
    }
}

return $permalink;
}

———————————————————————————————————

@Sally
The code above is limited to my programming level, I used your first answer and modified it… it is kind working in localhost but the archive url got 404 or blank page when I moved it to online hosting server…

2 Answers
2

(Updated/new answer)

So here I’m going straight to the code you need: (but do read the updated version of the original answer)

Register the CPT’s and Set the proper rewrite slug

  1. This is for the “English Speaking” CPT, which is assigned to the
    custom taxonomy “Speaking Task”:

    register_post_type( 'english-speaking', array(
        'label' => 'English Speaking',
        'public' => true,
        'rewrite' => array(
            'slug' => 'english/speaking/%speaking_task%/%question_id%',
        ),
        // Other args here.
    ) );
    
  2. This is for the “English Writing” CPT, which is not assigned to
    any taxonomy:

    register_post_type( 'english-writing', array(
        'label' => 'English Writing',
        'public' => true,
        'rewrite' => array(
            'slug' => 'english/writing/%question_id%',
        ),
        // Other args here.
    ) );
    

Register the custom taxonomy “Speaking Task”

register_taxonomy( 'speaking-task', array( 'english-speaking' ), array(
    'label' => 'Speaking Tasks',
    'public' => true,
    'rewrite' => array(
        'slug' => 'english/speaking',
    ),
    // Other args here.
) );

Make sure the rewrite slug is set to english/speaking.

The “English Speaking” archive would be accessible at:

  • http://example.com/english/speaking

  • http://example.com/english/speaking/{SPEAKING TASK SLUG}

Register the custom rewrite tags

add_rewrite_tag( '%question_id%', '(\d+)' );
add_rewrite_tag( '%speaking_task%', '([^/]+)' );

Add the code for replacing the custom rewrite tags

add_filter( 'post_type_link', 'my_filter_questions_post_type_link', 10, 2 );
function my_filter_questions_post_type_link( $post_link, $post ) {
    // Replaces/rewrites %question_id% in the permalink.
    if ( false !== strpos( $post_link, '%question_id%' ) ) {
        $id = get_post_meta( $post->ID, 'wpcf-question-id', true );

        // A default value is necessary, and the value has to be a 0.
        $id = $id ? $id : '0';

        $post_link = str_replace( '%question_id%', $id, $post_link );
    }

    // Replaces/rewrites %speaking_task% in the permalink.
    if ( false !== strpos( $post_link, '%speaking_task%' ) ) {
        // A default value is necessary, but the term/category doesn't need to
        // actually exists. So you could, for example, use 'all' as the value.
        $slug = 'uncategorized';

        $cats = get_the_terms( $post, 'speaking-task' );
        if ( $cats && ! is_wp_error( $cats ) ) {
            $slug = $cats[0]->slug;
        }

        $post_link = str_replace( '%speaking_task%', $slug, $post_link );
    }

    return $post_link;
}

Flush the rewrite rules

Refer to my other answer if you don’t know how to flush the rules..

Use get_query_var() to retrieve the rewrite tag values

  • get_query_var( 'question_id' ) for %question_id%

  • get_query_var( 'task_slug' ) for %task_slug%


The full sample code is available here. 🙂


UPDATE

If you use a plugin to register the post type, you can filter (set/change) the rewrite slug via the register_post_type_args filter, like so:

add_filter( 'register_post_type_args', 'my_filter_post_type_args', 10, 2 );
function my_filter_post_type_args( $args, $post_type ) {
    if ( ! isset( $args['rewrite'] ) ) {
        $args['rewrite'] = array();
    }

    if ( 'english-speaking' === $post_type ) {
        $args['rewrite']['slug'] = 'english/speaking/%speaking_task%/%question_id%';
    } elseif ( 'english-speaking' === $post_type ) {
        $args['rewrite']['slug'] = 'english/writing/%question_id%';
    }

    return $args;
}

And likewise if you use a plugin to register the custom taxonomy, you can filter (set/change) the rewrite slug via the register_taxonomy_args filter, like so:

add_filter( 'register_taxonomy_args', 'my_filter_taxonomy_args', 10, 2 );
function my_filter_taxonomy_args( $args, $taxonomy ) {
    if ( ! isset( $args['rewrite'] ) ) {
        $args['rewrite'] = array();
    }

    if ( 'speaking-task' === $taxonomy ) {
        $args['rewrite']['slug'] = 'english/speaking';
    }

    return $args;
}

You might need to set a lower priority — i.e. change the 10 to 11 or a higher value (number) — greater number = lower priority, whereas lower number = higher priority.

Alternatively, without custom coding, and if the plugin allows you to manually set the rewrite slug of the custom post type or taxonomy, then just use the appropriate box/field to enter the appropriate rewrite slug. =)

Leave a Comment