0

I have a CPT: 'Guide' and Custom Taxonomy 'Guide Category'.

What I want to achieve is:

The CPT permalink structure is: example.com/guides/custom-taxonomy/post-name/

So that the following should display the single-guide.php:

  • example.com/guides/database/best-dbms-for-web-apps/
  • example.com/guides/database/mysql/how-to-optimize-mysql-databases/

At the same time, I want these to show the taxonomy-guide-category.php:

  • example.com/guides/database/
  • example.com/guides/database/mysql/

Here's the code to register the CPT:

      $args = [
         'label'               => esc_html__('How-to Guides', 'text-domain'),
         'labels'              => $labels,
         'description'         => 'How-to Guides',
         'public'              => true,
         'hierarchical'        => false,
         'exclude_from_search' => false,
         'publicly_queryable'  => true,
         'show_ui'             => true,
         'show_in_nav_menus'   => true,
         'show_in_admin_bar'   => true,
         'show_in_rest'        => true,
         'query_var'           => true,
         'can_export'          => true,
         'delete_with_user'    => false,
         'has_archive'         => 'guides',
         'rest_base'           => '',
         'show_in_menu'        => true,
         'menu_position'       => '',
         'menu_icon'           => 'dashicons-welcome-learn-more',
         'capability_type'     => 'post',
         'supports'            => ['title', 'editor', 'thumbnail', 'author', 'comments'],
         'taxonomies'          => ['guide-category'],
         'rewrite'             => [
            // 'slug'       => 'guides',
            'slug'       => 'guides/%guide_category%',
            'with_front' => false,
         ],
      ];

      register_post_type('guide', $args);

And here is the custom taxonomy code:

   $args = [
      'label'              => esc_html__('Guide Categories', 'text-domain'),
      'labels'             => $labels,
      'description'        => 'How-to Guides Category',
      'public'             => true,
      'publicly_queryable' => true,
      'hierarchical'       => true,
      'show_ui'            => true,
      'show_in_menu'       => true,
      'show_in_nav_menus'  => true,
      'show_in_rest'       => true,
      'show_tagcloud'      => true,
      'show_in_quick_edit' => true,
      'show_admin_column'  => true,
      'query_var'          => true,
      'sort'               => false,
      'meta_box_cb'        => 'post_categories_meta_box',
      'rest_base'          => '',
      'rewrite'            => [
         'slug'         => 'guides',
         'with_front'   => false,
         'hierarchical' => true,
      ],
   ];
   register_taxonomy('guide-category', 'guide', $args);

This is the current code to achieve what I want:


/// STEP 1: Create Rewrite Rules
function xx_cpt_permalink_rewrite_rules() {

   // Guides CPT
   add_rewrite_rule(
      '^guides/([^/]+)/([^/]+)/?$',
      'index.php?post_type=guide&taxonomy=$matches[1]&name=$matches[2]',
      'top'
   );

   add_rewrite_rule(
      '^guides/([^/]+)/([^/]+)/([^/]+)/?$',
      'index.php?post_type=guide&taxonomy=$matches[1]&name=$matches[3]',
      'top'
   );

   add_rewrite_rule(
      '^guides/([^/]+)/([^/]+)/([^/]+)/([^/]+)/?$',
      'index.php?post_type=guide&taxonomy=$matches[1]&name=$matches[4]',
      'top'
   );
}
add_action('init', 'xx_cpt_permalink_rewrite_rules');


/// STEP 2: Create desired PERMALINKS
function xx_guide_permalink_structure($post_link, $post) {

   // Guides CPT
   if ('guide' === $post->post_type && $terms = get_the_terms($post->ID, 'guide-category')) {
      // Get categories in reverse order (parent first)
      $terms = array_reverse($terms);
      $category_links = array();
      foreach ($terms as $term) {
         $category_links[] = $term->slug;
      }

      $permalink = home_url('/guides/' . $post->post_type_link . implode('/', $category_links) . '/' . $post->post_name . '/');
   } else {
      $permalink = $post_link;
   }
   return $permalink;
}

add_filter('post_type_link', 'xx_guide_permalink_structure', 10, 2);


/// STEP 3:  (by default, while accessing:  "EXAMPLE.COM/category/postname"
// WP thinks, that a standard post is requested. So, we are adding CUSTOM POST TYPE into that query.
function xx_add_cpts_to_pre_get_posts_query($query) {
   if ($query->is_main_query() && !is_admin() && $query->is_single && 'guide' === $query->post_type) {
      $query->set('post_type',  array_merge(array('post'), array('guide')));
   }
   return $query;
}
add_action('pre_get_posts', 'xx_add_cpts_to_pre_get_posts_query',  12);

Now, the following URLs work as expected:

  • example.com/guides/database/best-dbms-for-web-apps/ => single-guide.php
  • example.com/guides/database/mysql/how-to-optimize-mysql-databases/ => single-guide.php
  • example.com/guides/database/ => taxonomy-guide-category.php

However, the following URLs do NOT work as expected:

  • example.com/guides/database/mysql/ => taxonomy-guide-category.php

How do I achieve the required results? What is wrong with my code and what could be improved/optimized?

Thanks in advance.

1 Answer 1

0

Permalink Structure Function

This function constructs the permalink for the 'guide' post type, including all hierarchical terms, with added spaces for style consistency:

function xx_guide_permalink_structure($post_link, $post) {
    if ( 'guide' === $post->post_type && $terms = get_the_terms( $post->ID, 'guide-category' ) ) {
        usort( $terms, function( $a, $b ) { return $a->parent - $b->parent; } ); // Sort terms by parent
        $category_links = array();
        foreach ( $terms as $term ) {
            if ( ! empty( $term->parent ) ) {
                $parent_term = get_term( $term->parent, 'guide-category' );
                if ( ! in_array( $parent_term->slug, $category_links ) ) {
                    $category_links[] = $parent_term->slug; // Add parent term slug if not already added
                }
            }
            $category_links[] = $term->slug; // Add current term slug
        }
        $permalink = home_url( '/guides/' . implode( '/', $category_links ) . '/' . $post->post_name . '/' );
        return $permalink;
    }
    return $post_link;
}
add_filter( 'post_type_link', 'xx_guide_permalink_structure', 10, 2 );

Rewrite Rules Function

This function sets up the custom rewrite rules to correctly interpret the URL structure for posts and taxonomy archives:

function xx_cpt_permalink_rewrite_rules() {
    // For posts (deep nesting allowed)
    add_rewrite_rule(
        '^guides/(([^/]+/)+)([^/]+)/?$',
        'index.php?guide-category=$matches[1]&post_type=guide&name=$matches[3]',
        'top'
    );

    // For taxonomy archive (deep nesting allowed)
    add_rewrite_rule(
        '^guides/(([^/]+/)+)?/?$',
        'index.php?taxonomy=guide-category&term=$matches[1]',
        'top'
    );
}
add_action( 'init', 'xx_cpt_permalink_rewrite_rules' );

Remember to flush the permalink settings.

3
  • Thanks @mukto90 I have applied your changes. Now, the ONLY working case is the first-level archive page (example.com/guides/database/). All other links are not working. Also, the parent category(s) have been removed from the post URL and only the direct child cat is prepended to the post URL: (example.com/guides/database/mysql/how-to-optimize-mysql-databases/ became => example.com/guides/mysql/how-to-optimize-mysql-databases/) and it does not work too!
    – Elgameel
    Commented Jun 17 at 11:51
  • I've updated my answer.
    – mukto90
    Commented Jun 17 at 13:14
  • Thanks again @mukto90 . For categories, the first-level cat is the only working case, all child-categories archives are not working. For the post_type_link post link includes the direct post cat and its direct parent-cat only (example.com/x/y/z/post-name/ => example.com/y/z/post-name/). Moreover, any post-link with any parent (or child) category will work even if the post is not in that category.
    – Elgameel
    Commented Jun 18 at 9:28

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