3

[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 . '/' . $task . '/' . $cqid . '/' . $post->post_name . '/' );
        $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...

3
  • after some research, seems like I need to use "is_front", but I could not find a good example ...
    – Atimmy
    Apr 20, 2018 at 4:14
  • Use this answer as a starting point.
    – Max Yudin
    Apr 20, 2018 at 7:02
  • Could be done using a placeholder in slug argument in rewrite parameter while registering the post type. Then a str_replace in post_type_link filter or add_rewrite_rule should do it.
    – Abhik
    Apr 21, 2018 at 5:11

2 Answers 2

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. =)

10
  • Hello Sally, sorry for the late response. Thank you for the code and teaching, much much appreciated.
    – Atimmy
    Aug 20, 2018 at 16:47
  • Can you please also teach me how to create a rewrite to a cpt archive page? ==================== CPT: What I am trying to accomplished is to have URL like this:
    – Atimmy
    Aug 20, 2018 at 16:53
  • Hi @Atimmy, your last comment seems incomplete.. can you re-post that, particularly after the "URL like this" part?
    – Sally CJ
    Aug 20, 2018 at 23:10
  • Hi Sally, I used your genius code and one tiny issue… (could be result of my minor modification ) The “$s “ seems to be reflecting the meta key value of “custom-question-id” instead of the “subject”. If I simply moved this line of code “$s = $cpt_list[ $post->post_type ];” out side the if statement, it then will reflect the subject....
    – Atimmy
    Aug 21, 2018 at 5:04
  • I am going to edit my question now because the requirement has changed and my questions can not be fit into comment
    – Atimmy
    Aug 21, 2018 at 5:05
2

(Updated/new answer)

For the original question

I changed my original answer because there's actually a way simpler solution (without having to use the add_rewrite_rule() function, or hooking to the parse_request filter) in achieving this permalink structure (for your "questions" CPT):

  • /{SLUG}/question/{TASK ID}/{POST SLUG}

For example, you could have these permalinks:

  • /science/question/2/science-question-1
  • /math/question/4/math-question-1
  • /english/question/4/english-question-1

So how to achieve that?

When you register a "question" CPT via the register_post_type() function, set the rewrite slug to {SLUG}/question/%task_id% where {SLUG} is any slug such as science, math, and english.

register_post_type( 'science-question', array(
    'label' => 'Science Questions',
    'public' => true,
    'rewrite' => array(
        // The rewrite slug. You can change 'science' to whatever you like.
        'slug' => 'science/question/%task_id%',
    ),
    // Other args here.
) );

register_post_type( 'math-question', array(
    'label' => 'Math Questions',
    'public' => true,
    'rewrite' => array(
        // The rewrite slug. You can change 'math' to whatever you like.
        'slug' => 'math/question/%task_id%',
    ),
    // Other args here.
) );

Then for the custom taxonomy "Task" (registered via the register_taxonomy() function), set the taxonomy slug to task like this example:

// Set the slug to 'task'.
register_taxonomy( 'task', array( '{POST TYPE}', '{POST TYPE}', ... ), array(
    'label' => 'Tasks',
    'public' => true,
    // Other args here.
) );

(The {POST TYPE} could be science-question, math-question, etc.)

Next, add this code which will register the custom rewrite tag %task_id%:

add_rewrite_tag( '%task_id%', '(\d+)' );

And add this code which will replace the %task_id% tag in the post permalink:

// Replaces %task_id% in the permalink (i.e. $post_link).
add_filter( 'post_type_link', 'my_filter_questions_post_type_link', 10, 2 );
function my_filter_questions_post_type_link( $post_link, $post ) {
    if ( false !== strpos( $post_link, '%task_id%' ) ) {
        $cats = get_the_terms( $post, 'task' );
        $cat_id = 0;

        // If the post was assigned to at least one "task" category.
        if ( $cats && ! is_wp_error( $cats ) ) {
            $cat_id = $cats[0]->term_id;
        }
        // Else, /{SLUG}/question/0/{POST SLUG} should work well. =) But of
        // course, all "question" CPT posts should be assigned to *at least*
        // one "task".

        $post_link = str_replace( '%task_id%', $cat_id, $post_link );
    }

    return $post_link;
}

The full sample code is available here. :)

Don't forget to flush the rewrite rules!

It is necessary and here's how: Just go to the "Permalink Settings" page. That's all.

But if the rewrite rules were not flushed (you can confirm by visiting the "question" page), then on the "Permalink Settings" page, click on the "Save Changes" button without having to make any changes.

How to get the value of task_id

Use get_query_var( 'task_id' ). Example:

$task_id = get_query_var( 'task_id' );
$term = get_term( $task_id );

PS: I know the question has changed, but I updated this answer because it could help someone else. ;)

18
  • Hi Sally, thanks a million! Could you kindly provide an example where if the CPT does not have consistent slug format of "{subject}-questions}"... do I use is_front = true in the code ?
    – Atimmy
    Apr 21, 2018 at 1:03
  • Hi @Atimmy. where did you see the is_front? I don't actually know if that's relevant to the WordPress URL rewriting. But if you have "questions" CPT that doesn't end with or doesn't even use -question in its key/slug, then there's a way to fix that.
    – Sally CJ
    Apr 21, 2018 at 3:04
  • I've updated the code, so that it doesn't rely on {subject}-question. Try it out and let me know if you need any further assistance.
    – Sally CJ
    Apr 21, 2018 at 4:42
  • The code looks awesome, I will try it out and let you know how things go. Thank you again. By the way, are you available for hire at all? I might need more WP help in nearby future.
    – Atimmy
    Apr 21, 2018 at 7:19
  • For the time being, I'm not available for hire. Thanks though, for asking. =) And hopefully the code works for you.
    – Sally CJ
    Apr 21, 2018 at 9:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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