1

I've come quite far, but can't manage to get this to work.

I'd like to remove the base slug from my Custom Post Type review and my Custom Taxonomy brand.

The final result should be a URL like this: https://example.org/apple/iphone7.

Right now I got this: https://example.org/review/apple/iphone7.

I've read quite a lot and I know about the consequences and possible conflicts and that WordPress isn't designed to handle these kind of rewrites. But there has to be a way to achieve what I'm trying to do.

The code at http://www.markwarddesign.com/2014/02/remove-custom-post-type-slug-permalink/ works to remove the base slug, but I can't manage to combine the functions with mine below. As soon as I add the function from markwarddesign.com it results in a 404.

I tried the solution posted at Custom Taxonomy specific to a Custom Post type but it still contains the base slug.

Please take a look at my setup.

function brand_permalink($permalink, $post_id, $leavename) {
    if (strpos($permalink, '%brand%') === FALSE) return $permalink;
        // Get post
        $post = get_post($post_id);
        if (!$post) return $permalink;

        // Get taxonomy terms
        $terms = wp_get_object_terms($post->ID, 'brand');
        if (!is_wp_error($terms) && !empty($terms) && is_object($terms[0]))
            $taxonomy_slug = $terms[0]->slug;
        else $taxonomy_slug = 'other';

    return str_replace('%brand%', $taxonomy_slug, $permalink);
}
add_filter('post_link', 'brand_permalink', 1, 3);
add_filter('post_type_link', 'brand_permalink', 1, 3);

/**
 * Code below is from https://wordpress.stackexchange.com/questions/57493/custom-taxonomy-specific-to-a-custom-post-type
 * A custom taxonomy is created and linked to CPT 'review'.
 * The goal is to create permalinks containing the taxonomy + CPT post name, e.g. /some-brand/xyz-review/
 */
function custom_brand_taxonomy() {
    register_taxonomy(
        'brand',  //The name of the taxonomy. Name should be in slug form (must not contain capital letters or spaces). 
        'review',        //post type name
        array(  
            'hierarchical' => true,  
            'label' => 'Brand',  //Display name
            'query_var' => true,
            'rewrite' => array(
                'slug' => '', // This controls the base slug that will display before each term
                'with_front' => false // Don't display the category base before 
            )
        )  
    );  
}  
add_action( 'init', 'custom_brand_taxonomy');

/**
 * Creating a function to create our CPT
 *
 */
function xyz_custom_post_types() {

    // Set options for Custom Post Type REVIEW
    $review_args = array(
        'label'               => __( 'review', 'mythemexyz' ),
        'description'         => __( 'Descrption review bla bla', 'mythemexyz' ),
        'labels'              => array(
                                'name'                => _x( 'reviewe', 'Post Type General Name', 'mythemexyz' ),
                                'singular_name'       => _x( 'review', 'Post Type Singular Name', 'mythemexyz' ),
                                'menu_name'           => __( 'reviewe', 'mythemexyz' ),
                                'parent_item_colon'   => __( 'Parent review', 'mythemexyz' ),
                                'all_items'           => __( 'Alle reviewe', 'mythemexyz' ),
                                'view_item'           => __( 'review ansehen', 'mythemexyz' ),
                                'add_new_item'        => __( 'review erstellen', 'mythemexyz' ),
                                'add_new'             => __( 'Erstellen', 'mythemexyz' ),
                                'edit_item'           => __( 'review bearbeiten', 'mythemexyz' ),
                                'update_item'         => __( 'review aktualisieren', 'mythemexyz' ),
                                'search_items'        => __( 'review suchen', 'mythemexyz' ),
                                'not_found'           => __( 'Nicht gefunden', 'mythemexyz' ),
                                'not_found_in_trash'  => __( 'Nicht in Papierkorb gefunden', 'mythemexyz' ),
                                ),
        // Features this CPT supports in Post Editor
        'supports'            => array( 'title', 'editor', 'revisions', 'custom-fields', 'page-attributes' ),
        /* A hierarchical CPT is like Pages and can have
        * Parent and child items. A non-hierarchical CPT
        * is like Posts.
        */  
        'hierarchical'        => false,
        'public'              => true,
        'show_ui'             => true,
        'show_in_menu'        => true,
        'show_in_nav_menus'   => true,
        'show_in_admin_bar'   => true,
        'menu_position'       => 99,
        'can_export'          => true,
        'rewrite'             => array( 'slug' => 'review/%brand%', 'with_front' => false ),
        'has_archive'         => 'review',
        'exclude_from_search' => false,
        'publicly_queryable'  => true,
        'capability_type'     => 'page',
    );

    // Registering your Custom Post Type
    register_post_type( 'review', $review_args );

}

/* Hook into the 'init' action so that the function
 * Containing our post type registration is not 
 * unnecessarily executed. 
 */
add_action( 'init', 'xyz_custom_post_types', 0 );

Any help is appreciated.

  • what are your post permalinks set to? – Milo Jan 28 '16 at 18:54
  • Sorry forgot about that, it's the regular %postname% – f8m Jan 28 '16 at 19:58
1

I copied your code as-is above, pasted it into the twentysixteen theme, and changed just the post type rewrite slug from review/%brand% to %brand%. This resulted in both the term archive and review posts having your desired URL structure and successfully displaying.

Now the bad news is that the rewrite rules generated for the taxonomy and post type stomp all over the post and page rewrite rules. A request for a post results in WordPress trying to find a brand term matching your post slug. A request for parent-page/child-page results in WordPress trying to query a review post matching child-page.

The good news is that we can fix this. When a request is parsed by WordPress, all of the potentially matching query vars are populated, and are passed through the request filter where we can modify and transform them.

To peek into this filter, try:

function test_request( $request ){
    echo '<pre>';
    print_r($request);
    echo '</pre>';
}
add_filter( 'request', 'test_request' );

Then visit different types of pages and see what query vars get populated.

I hacked together a quick example that fixes basic post and child page display. This doesn't cover everything that might be broken. I haven't tested paginated posts, attachments, other stuff?? This also may change if you've got other post types, or your post permalink structure changes. But this should give you a starting point for getting things working.

function wpd_fix_requests( $request ){

    // if it's a brand term request
    // see if a brand term exists by this name with get_term_by
    // if not, reset the query to a post or page with name
    if( array_key_exists( 'brand' , $request )
        && ! array_key_exists( 'post_type' , $request )
        && ! get_term_by( 'slug', $request['brand'], 'brand' ) ){
            $request['name'] = $request['brand'];
            $request['post_type'] = array( 'post', 'page' );
            unset( $request['brand'] );
    }

    // if this is a review request
    // add page to post type in case it's a child page
    if( array_key_exists( 'review', $request ) ){
        $request['post_type'] = array( 'review', 'page' );
    }

    // return request vars
    return $request;
}
add_filter( 'request', 'wpd_fix_requests' );
  • Thanks a lot for your thorough answer @Milo, this seems to work pretty well! I made one more fix by changing the slug of my custom_brand_taxonomy from 'slug' => '' to 'slug' => '/' to remove the base slug, e.g. /brand/apple -> /apple/. I will update here if I come across any broken things. Appreciate your help! – f8m Jan 29 '16 at 19:13
  • I came across a different problem. Whenever I create a regular page or post it gets displayed by single.php, even for pages. If I assign a custom page template it gets ignored. Do you have an idea why this happens? – f8m Jan 31 '16 at 14:51
  • hmm, you're right. It gets the body classes right, but not the templates and is_ conditionals. I'll look into fixing that when I've got the chance. In the meantime, you can force templates with the single_template filter. Custom page templates are stored under the meta key _wp_page_template. – Milo Jan 31 '16 at 19:05
  • Thanks a lot, I'll give it a try. I didn't know about the single_template filter. If you get the chance to look into it I'd be really grateful. Let me know how I can buy you a beer :) – f8m Jan 31 '16 at 21:27
  • Did you have a chance to look at the problem? – f8m Feb 4 '16 at 12:17
1

For anyone interested, this was the full solution to my problem. So far it works quite good.

I use %postname% as the permalink structure and have my Custom Taxonomy working as my Brand and the Custom Post Type working as my Product.

So my URL looks like this example.org/some-brand/some-product while I can display an overview on example.org/some-brand/.

I'm using the Advanced Custom Fields Pro plugin to make all my pages editable. The function wp323_get_template_file implements what @Milo suggested, since all requests to pages or pages with a custom page template were redirected to single.php. Now pages get displayed by page.php and if there's a custom page template the proper template is displayed.

Every Product has to be linked to a Brand, otherwise the URL will contain /other/ instead of the brand.

Hope this helps someone, took me a few nights to accomplish this. Thanks to @Milo.

 <?php

 function wpd_fix_requests( $request ){
     // Written by @Milo: https://wordpress.stackexchange.com/questions/215987/remove-base-slug-in-cpt-ct-use-ct-in-permalink
     // if it's a brand term request
     // see if a brand term exists by this name with get_term_by
     // if not, reset the query to a post or page with name
     if( array_key_exists( 'brand' , $request )
         && ! array_key_exists( 'post_type' , $request )
         && ! get_term_by( 'slug', $request['brand'], 'brand' ) ){
             $request['name'] = $request['brand'];
             $request['post_type'] = array( 'post', 'page' );
             unset( $request['brand'] );
     }

     // if this is a review request
     // add page to post type in case it's a child page
     if( array_key_exists( 'review', $request ) ){
         $request['post_type'] = array( 'review', 'page' );
     }

     // return request vars
     return $request;
 }
 add_filter( 'request', 'wpd_fix_requests' );

 function brand_permalink($permalink, $post_id, $leavename) {
     if (strpos($permalink, '%brand%') === FALSE) return $permalink;
         // Get post
         $post = get_post($post_id);
         if (!$post) return $permalink;

         // Get taxonomy terms
         $terms = wp_get_object_terms($post->ID, 'brand');
         if (!is_wp_error($terms) && !empty($terms) && is_object($terms[0]))
             $taxonomy_slug = $terms[0]->slug;
         else $taxonomy_slug = 'other';

     return str_replace('%brand%', $taxonomy_slug, $permalink);
 }
 add_filter('post_link', 'brand_permalink', 1, 3);
 add_filter('post_type_link', 'brand_permalink', 1, 3);

 /**
  * Code below is from https://wordpress.stackexchange.com/questions/57493/custom-taxonomy-specific-to-a-custom-post-type
  * A custom taxonomy is created and linked to CPT 'review'.
  * The goal is to create permalinks containing the taxonomy + CPT post name, e.g. /some-brand/xyz-review/
  */
 function custom_brand_taxonomy() {
     register_taxonomy(
         'brand',  //The name of the taxonomy. Name should be in slug form (must not contain capital letters or spaces). 
         'review',        //post type name
         array(  
             'hierarchical' => true,  
             'label' => 'Brand',  //Display name
             'query_var' => true,
             'rewrite' => array(
                 'slug' => '/', // This controls the base slug that will display before each term
                 'with_front' => false // Don't display the category base before 
             )
         )  
     );  
 }  
 add_action( 'init', 'custom_brand_taxonomy');

 /**
  * Creating a function to create our CPT
  *
  */
 function xyz_custom_post_types() {

     // Set options for Custom Post Type REVIEW
     $review_args = array(
         'label'               => __( 'review', 'mythemexyz' ),
         'description'         => __( 'Descrption review bla bla', 'mythemexyz' ),
         'labels'              => array(
                                 'name'                => _x( 'reviewe', 'Post Type General Name', 'mythemexyz' ),
                                 'singular_name'       => _x( 'review', 'Post Type Singular Name', 'mythemexyz' ),
                                 'menu_name'           => __( 'reviewe', 'mythemexyz' ),
                                 'parent_item_colon'   => __( 'Parent review', 'mythemexyz' ),
                                 'all_items'           => __( 'Alle reviewe', 'mythemexyz' ),
                                 'view_item'           => __( 'review ansehen', 'mythemexyz' ),
                                 'add_new_item'        => __( 'review erstellen', 'mythemexyz' ),
                                 'add_new'             => __( 'Erstellen', 'mythemexyz' ),
                                 'edit_item'           => __( 'review bearbeiten', 'mythemexyz' ),
                                 'update_item'         => __( 'review aktualisieren', 'mythemexyz' ),
                                 'search_items'        => __( 'review suchen', 'mythemexyz' ),
                                 'not_found'           => __( 'Nicht gefunden', 'mythemexyz' ),
                                 'not_found_in_trash'  => __( 'Nicht in Papierkorb gefunden', 'mythemexyz' ),
                                 ),
         // Features this CPT supports in Post Editor
         'supports'            => array( 'title', 'editor', 'revisions', 'custom-fields', 'page-attributes' ),
         /* A hierarchical CPT is like Pages and can have
         * Parent and child items. A non-hierarchical CPT
         * is like Posts.
         */  
         'hierarchical'        => false,
         'public'              => true,
         'show_ui'             => true,
         'show_in_menu'        => true,
         'show_in_nav_menus'   => true,
         'show_in_admin_bar'   => true,
         'menu_position'       => 99,
         'can_export'          => true,
         'rewrite'             => array( 'slug' => '%brand%', 'with_front' => false ),
         'has_archive'         => 'review',
         'exclude_from_search' => false,
         'publicly_queryable'  => true,
         'capability_type'     => 'page',
     );

     // Registering your Custom Post Type
     register_post_type( 'review', $review_args );

 }

 /* Hook into the 'init' action so that the function
  * Containing our post type registration is not 
  * unnecessarily executed. 
  */
 add_action( 'init', 'xyz_custom_post_types', 0 );

 /**
  * Use single_template filter to properply redirect to page.php and custom page templates
  */
 function wp323_get_template_file($single_template) {
      global $post;

      $page_custom_template = get_post_meta( $post->ID, '_wp_page_template', true );

      if ($post->post_type == 'page') {
        if($page_custom_template != 'default') {
            $single_template = dirname( __FILE__ ) . '/' . $page_custom_template;
        }
        else {
            $single_template = dirname( __FILE__ ) . '/page.php';
        }

      }
      return $single_template;
 }
 add_filter( 'single_template', 'wp323_get_template_file' );

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.