0

I'm trying to make 2 custom post types (authors and works) share the same slug. The authors CPT uses an archive page which functions properly, but works CPT does not need one.

CPT 1: https://domain.com/library/authors/author-name/

CPT 2: https://domain.com/library/authors/author-name/works-name/

The permalink rewrite I cobbled together is functional. It pulls the author post ID from a custom meta menu in the works CPT post and then builds the permalink. However, I get a 404 error when visiting a works link because WordPress can't find the works post in the database due to the sharing of slugs.

What I can't figure out is how to query the works post so it can go around usual query that fails and locate the post. I tried the only 2 examples I found on here and stackoverflow, but they aren't working. I tried changing authors CPT to hierarchical (just in case) but didn't seem to matter. Been at this for days and this type of stuff is way over my head, so maybe I'm just blind to the obvious.

Post Creation Code

////////////////////////////////
//////// Works POST TYPE
function works_post_type() {
    $labels = array(
        'name'                  => _x( 'Works', 'Post Type General Name', 'text_domain' ),
        'singular_name'         => _x( 'Work', 'Post Type Singular Name', 'text_domain' ),
        'menu_name'             => __( 'Works', 'text_domain' ),
        'name_admin_bar'        => __( 'Works', 'text_domain' ),
        'archives'              => __( 'Works Archives', 'text_domain' ),
        'parent_item'           => null,
        'parent_item_colon'     => null,
        'all_items'             => __( 'All Works', 'text_domain' ),
        'add_new_item'          => __( 'Add New Work', 'text_domain' ),
        'add_new'               => __( 'Add New Work', 'text_domain' ),
        'new_item'              => __( 'New Work', 'text_domain' ),
        'edit_item'             => __( 'Edit Work', 'text_domain' ),
        'update_item'           => __( 'Update Work', 'text_domain' ),
        'view_item'             => __( 'View Work', 'text_domain' ),
        'search_items'          => __( 'Search Works', 'text_domain' ),
        'not_found'             => __( 'Works Not found', 'text_domain' ),
        'not_found_in_trash'    => __( 'Works Not found in Trash', 'text_domain' ),
        'featured_image'        => __( 'Featured Image', 'text_domain' ),
        'set_featured_image'    => __( 'Set featured image', 'text_domain' ),
        'remove_featured_image' => __( 'Remove featured image', 'text_domain' ),
        'use_featured_image'    => __( 'Use as featured image', 'text_domain' ),
        'insert_into_item'      => __( 'Insert into Work', 'text_domain' ),
        'uploaded_to_this_item' => __( 'Uploaded to this Work', 'text_domain' ),
        'items_list'            => __( 'Works list', 'text_domain' ),
        'items_list_navigation' => __( 'Works list navigation', 'text_domain' ),
        'filter_items_list'     => __( 'Filter Works items list', 'text_domain' ),
    );
    $args = array(
        'label'                 => __( 'Works', 'text_domain' ),
        'description'           => __( 'Written works', 'text_domain' ),
        'labels'                => $labels,
        'supports'              => array( 'title', 'editor', 'excerpt', 'thumbnail', 'revisions', 'custom-fields', 'comments', 'page-attributes'),
        'taxonomies'            => array( 'post_tag' ),
        'hierarchical'          => true,
        'public'                => true,
        'show_ui'               => true,
        'show_in_menu'          => true,
        'menu_position'         => 2,
        'menu_icon'             => 'dashicons-book-alt',
        'show_in_admin_bar'     => true,
        'show_in_nav_menus'     => true,
        'can_export'            => true,
        'has_archive'           => false,
        'exclude_from_search'   => false,
        'publicly_queryable'    => true,
        'query_var'             => true,
        'capability_type'       => 'post',
    );
    register_post_type( 'works', $args );
}
add_action( 'init', 'works_post_type', 0 );
////////////////////////////////
//////// AUTHOR POST TYPE
function authors_post_type() {
    $labels = array(
        'name'                  => _x( 'Authors', 'Post Type General Name', 'text_domain' ),
        'singular_name'         => _x( 'Author', 'Post Type Singular Name', 'text_domain' ),
        'menu_name'             => __( 'Authors', 'text_domain' ),
        'name_admin_bar'        => __( 'Authors', 'text_domain' ),
        'archives'              => __( 'Authors Archives', 'text_domain' ),
        'parent_item'           => null,
        'parent_item_colon'     => null,
        'all_items'             => __( 'All Authors', 'text_domain' ),
        'add_new_item'          => __( 'Add New Author', 'text_domain' ),
        'add_new'               => __( 'Add New Author', 'text_domain' ),
        'new_item'              => __( 'New Author', 'text_domain' ),
        'edit_item'             => __( 'Edit Author', 'text_domain' ),
        'update_item'           => __( 'Update Author', 'text_domain' ),
        'view_item'             => __( 'View Author', 'text_domain' ),
        'search_items'          => __( 'Search Authors', 'text_domain' ),
        'not_found'             => __( 'Authors Not found', 'text_domain' ),
        'not_found_in_trash'    => __( 'Authors Not found in Trash', 'text_domain' ),
        'featured_image'        => __( 'Featured Image', 'text_domain' ),
        'set_featured_image'    => __( 'Set featured image', 'text_domain' ),
        'remove_featured_image' => __( 'Remove featured image', 'text_domain' ),
        'use_featured_image'    => __( 'Use as featured image', 'text_domain' ),
        'insert_into_item'      => __( 'Insert into Author', 'text_domain' ),
        'uploaded_to_this_item' => __( 'Uploaded to this Author', 'text_domain' ),
        'items_list'            => __( 'Authors list', 'text_domain' ),
        'items_list_navigation' => __( 'Authors list navigation', 'text_domain' ),
        'filter_items_list'     => __( 'Filter Authors items list', 'text_domain' ),
    );
    $args = array(
        'label'                 => __( 'Authors', 'text_domain' ),
        'description'           => __( 'Written authors', 'text_domain' ),
        'labels'                => $labels,
        'supports'              => array( 'title', 'thumbnail', 'editor', 'revisions', 'custom-fields'),
        'taxonomies'            => array( 'post_tag' ),
        'hierarchical'          => true,
        'public'                => true,
        'show_ui'               => true,
        'show_in_menu'          => true,
        'menu_position'         => 2,
        'menu_icon'             => 'dashicons-businessperson',
        'show_in_admin_bar'     => true,
        'show_in_nav_menus'     => true,
        'can_export'            => true,
        'has_archive'           => true,        
        'exclude_from_search'   => false,
        'publicly_queryable'    => true,
        'query_var'             => true,
        'rewrite'               => array('slug' => 'library/authors'),
        'capability_type'       => 'post',
    );
    register_post_type( 'authors', $args );
}
add_action( 'init', 'authors_post_type', 0 );
add_theme_support( 'post-thumbnails', array( 'authors' ) );

Permalink Rewrite (works)

add_filter( 'post_type_link', 'my_website_filter_post_type_link', 1, 4 );
function my_website_filter_post_type_link( $post_link, $post, $leavename, $sample ) {
    switch( $post->post_type ) {

        case 'works':

            $author_id = get_post_meta( $post->ID, 'wrks_author', true);
            $author = basename(get_permalink($author_id));
            if ( $author !== '' ) {

                // create the new permalink
                $post_link = home_url( user_trailingslashit( 'library/authors/' . $author . '/' . $post->post_name ) );
            }

            break;

    }
    return $post_link;
}

Query Fix Option 1 (failed)

add_filter('request', 'overwriteQueryVars', 10, 1);

function overwriteQueryVars($query)
{
    if ( empty($query['post_type']) || $query['post_type']!='authors' ) {
        return $query;
    }

    $sql = "SELECT ID FROM wp_posts WHERE post_type='authors' and post_name='%s'";
    $post_name = urlencode($query['name']);

    if ( in_authors_but_not_works ) {
        return $query;
    }

    $post_name = $query->request;
    $postType = 'works';
    
    $query = array (
        'page' => '',
        'works' => $post_name,
        'post_type' => $postType,
        'name' => $post_name,
    );

}

Query Fix Option 2 (failed)

add_action('parse_request', 'overwriteRequest', 10, 1);
function overwriteRequest($query) {

    if ( count($query->query_vars) > 0 && empty($query->query_vars['post_type'])) {

        $pageName = preg_replace('/library\/authors\//', '', $query->request);
        $postType = 'works';

        $result = get_posts(array(
            'post_type' => $postType,
            'name' => $pageName
        ));

        if (count($result) < 1) {

            return $query;

        } else {
            $new_query = array(
                'page' => '',
                'works' => $pageName,
                'post_type' => $postType,
                'name' => $pageName
            );
            $query->query_vars = $new_query;
            return $query;
        }

    } else {
        return $query;
    }
}

1 Answer 1

2

Note that a filter callback must always return something which is commonly the first parameter passed to the callback. So for example your overwriteQueryVars() function, it must always return the $query because request is a filter hook.

Secondly, instead of overwriting the entire query arguments (e.g. by doing $query = array( ... ) or $query->query_vars = $new_query;), you should only change the ones need to be changed in the existing query arguments, e.g. $query['post_type'] = $postType;.

But anyway, try this which worked well for me:

add_filter( 'request', 'overwriteQueryVars' );
function overwriteQueryVars( $query_vars ) { // I renamed $query to $query_vars.
    global $wp;

    /*
     * 1. Check if the current page URL path begins with library/authors/ as in example.com/library/authors/
     * 2. Then check if the post_type is exactly "authors".
     * 3. Finally, check if the "authors" is in the form of <slug>/<slug>.
     */
    if ( preg_match( '#^library/authors/#', $wp->request )        &&
        isset( $query_vars['post_type'], $query_vars['authors'] ) &&
        'authors' === $query_vars['post_type']                    &&
        preg_match( '#^([^/]+)/([^/]+)$#', $query_vars['authors'] )
    ) {
        $works_slug = explode( '/', $query_vars['authors'] )[1];

        // Find "works" post with the above slug.
        $ids = get_posts( array(
            'post_type' => 'works',
            'name'      => $works_slug,
            'fields'    => 'ids',
        ) );

        // If the post exists, then load it.
        if ( ! empty( $ids ) ) {
            $query_vars['post_type'] = 'works';
            $query_vars['name'] = $query_vars['works'] = get_page_uri( $ids[0] );

            unset( $query_vars['authors'] );
        }
    }

    return $query_vars; // ALWAYS RETURN IT
}

Additionally, in the post_type_link filter callback, I would:

  1. Change the 'library/authors/' . $author . '/' . $post->post_name to 'library/authors/' . $author . '/' . ( $leavename ? '%postname%' : $post->post_name ) to ensure the post permalink (the slug part) is editable via the post editing screen (in wp-admin).

  2. Change the $author = basename(get_permalink($author_id)); to $author = $author_id ? get_post_field( 'post_name', $author_id ) : '';.

Update

Sorry, I forgot about preview URLs (e.g. for posts with the status draft or pending) where the URL is not "pretty" and looks like https://example.com/?post_type=works&p=123&preview=true.

So to ensure the URL returned is correct, in your my_website_filter_post_type_link() function, add this at the very top:

if ( $sample || wp_force_plain_post_permalink( $post ) ) {
    return $post_link;
}

I.e. Add it above the line having the switch( $post->post_type ).

Also (to other readers), if your works post type is not hierarchical, replace the get_page_uri( $ids[0] ) with $works_slug.

7
  • 1
    It works! I wish I could say thank you in 50 languages. Thank you! nonetheless. I have been trying to get this to work on and off for over a year, posted numerous times with no solution. I wish I could repay you somehow. I hope you have the greatest day imaginable :)
    – liquidRock
    Aug 24, 2021 at 9:54
  • One thing I just noticed—I get strange behavior when trying to preview a draft post. If it's a draft works post, it previews the author page instead of the post. If it's a nested draft works post (nested under another works post that is also a draft or published) it gives a 404 error. Previewing an author post that is draft or published works as expected. I'm not sure what the problem with the regular draft is, but nested draft posts are not appending the parent post prefix slug and they also don't have ?preview=true at the end of the url. If you help address this, I would appreciate it.
    – liquidRock
    Aug 24, 2021 at 12:25
  • 1
    Yes, it always has wrks_author. I'm using WordPress Fieldmanager to create the metabox/menu and it loads in all of the authors in a menu using Datasource. As such, it always defaults to an author when you make a new works post—a works post cannot exist without the wrks_author being set.
    – liquidRock
    Aug 24, 2021 at 15:07
  • 1
    I see. But anyway, check the "Update" section in my revised answer.
    – Sally CJ
    Aug 25, 2021 at 8:42
  • 1
    That did the trick. Thanks so much for all of your help.
    – liquidRock
    Aug 25, 2021 at 12:36

Your Answer

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

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