0

In my theme I've created a new Custom Post Type webinar. As we only post 1 webinar per month, we would like to use the permalink structure -

/webinar/%year%/%monthnum%/

where we don't use /%day%/ and/or /%postname%/

Is this possible?

Currently it is showing as a month archive, where

/webinar/2018/08/

has the title

Archives for August 2018

where it is not just a Single Post


Attributes:
WordPress (4.9.8)
Genesis Framework (2.6.1)
Dynamik Theme (2.3.1)
https://cobaltapps.com


I am using the Custom Post Type UI plugin to create my custom post types - https://wordpress.org/plugins/custom-post-type-ui/

It auto generated the following code -

function cptui_register_my_cpts_webinars() {

    /**
     * Post Type: Webinars.
     */

    $labels = array(
        "name" => __( "Webinars", "" ),
        "singular_name" => __( "Webinar", "" ),
        "menu_name" => __( "UDEO Webinars", "" ),
        "all_items" => __( "All Webinars", "" ),
        "add_new" => __( "Add New", "" ),
        "add_new_item" => __( "Add New Webinar", "" ),
        "edit_item" => __( "Edit Webinar", "" ),
        "new_item" => __( "New Webinar", "" ),
        "view_item" => __( "View Webinar", "" ),
        "view_items" => __( "View Webinars", "" ),
        "search_items" => __( "Search Webinar", "" ),
        "not_found" => __( "No Webinars Found", "" ),
        "not_found_in_trash" => __( "No Webinars found in Trash", "" ),
        "archives" => __( "Webinar Archives", "" ),
    );

    $args = array(
        "label" => __( "Webinars", "" ),
        "labels" => $labels,
        "description" => "",
        "public" => true,
        "publicly_queryable" => true,
        "show_ui" => true,
        "show_in_rest" => false,
        "rest_base" => "",
        "has_archive" => false,
        "show_in_menu" => true,
        "show_in_nav_menus" => true,
        "exclude_from_search" => false,
        "capability_type" => "post",
        "map_meta_cap" => true,
        "hierarchical" => false,
        "rewrite" => array( "slug" => "webinars", "with_front" => true ),
        "query_var" => true,
        "supports" => array( "title", "editor", "thumbnail", "custom-fields", "comments", "author" ),
    );

    register_post_type( "webinars", $args );
}

add_action( 'init', 'cptui_register_my_cpts_webinars' );
4
  • can you edit your question and add the code you use to register the post type? I'm curious how you got date archives in the first place, since that is something WordPress can't do on its own with a custom post type.
    – Milo
    Sep 27, 2018 at 3:10
  • @Milo updated with plugin used to create custom post type
    – Sean
    Sep 27, 2018 at 3:18
  • probably need to also note the theme and templates it uses as this is likely affecting the output.
    – majick
    Sep 27, 2018 at 3:51
  • @majick I have added the framework and theme
    – Sean
    Sep 27, 2018 at 3:58

1 Answer 1

1

I'm not sure how your existing setup is working, since I can't reproduce the month/year archive behavior using your code with the default twentysixteen theme, however, the solution is perhaps the same, but perhaps not.

One thing first- I recommend against using the post publish date for the year/month, it just limits your flexibility. I'm going to use post metadata to store the year and month, under the keys webinar_year and webinar_month. You can add a custom meta box for these if you want to make it simple for end users, and you can also hook into the post save process and introduce some automation.

So the first step is to add a couple of new rewrite tags and define a new permastruct for your post type-

function cptui_register_my_cpts_webinars() {

    // your existing post type registration code
    // $args, etc..
    register_post_type( "webinars", $args );

    add_rewrite_tag( '%webinar_year%', '([0-9]{4})' );
    add_rewrite_tag( '%webinar_month%', '([0-9]{2})' );
    add_permastruct( 'webinars', 'webinar/%webinar_year%/%webinar_month%/' );

}
add_action( 'init', 'cptui_register_my_cpts_webinars' );

Don't forget to flush rewrite rules after you add this.

Then you'll need a filter on the post link to substitute the webinar_year and webinar_month meta values for the placeholders-

function webinars_permalink( $post_link, $post ) {
    if( 'webinars' == $post->post_type ) {
        $webinar_year = ( ! get_post_meta( $post->ID, 'webinar_year', true ) ) ? '0000' : get_post_meta( $post->ID, 'webinar_year', true );
        $webinar_month = ( ! get_post_meta( $post->ID, 'webinar_month', true ) ) ? '00' : get_post_meta( $post->ID, 'webinar_month', true );
        $post_link = str_replace( ['%webinar_year%', '%webinar_month%'], [$webinar_year, $webinar_month], $post_link );
    }
    return $post_link;
}
add_filter( 'post_type_link', 'webinars_permalink', 10, 2 );

Now you should be able to create these posts and see the correct URL with year and month values substituted. But they still won't work when you visit them.

These requests are currently meaningless to WordPress, so we need to intercept them, figure out what post they are for, then correct the query so WordPress can find it. We do this with a filter on request, which fires early, before WordPress makes any decisions about what the query is for-

function webinars_request( $request ) {
    if( isset( $request['webinar_year'] ) && isset( $request['webinar_month'] ) ){
        $webinar = new WP_Query([
            'post_type' => 'webinars',
            'posts_per_page' => 1,
            'meta_query' => [
                [
                    'key' => 'webinar_year',
                    'value' => $request['webinar_year']
                ],
                [
                    'key' => 'webinar_month',
                    'value' => $request['webinar_month']
                ]
            ]
        ]);
        if( $webinar->have_posts() ){
            $request['post_type'] = 'webinars';
            $request['webinars'] = $webinar->post->post_name;
            $request['name'] = $webinar->post->post_name;
        }
    }
    return $request;
}
add_filter( 'request', 'webinars_request' );

Here we're just querying for one post with the matching meta keys/values. It's going to return the most recent one with the requested values, so enforcement of uniqueness here is on you.

No, it's not the most elegant. WordPress only treats post slug as the unique thing it identifies a singular post query by, so anything else you want to shoehorn in there is a bit of a manual endeavor.

2
  • I am able to add the last 2 blocks to my function.php, just trying to find where Custom Post Type UI saves the function cptui_register_my_cpts_webinars() to add the 3 extra lines of code. will respond once I can test.
    – Sean
    Sep 27, 2018 at 5:14
  • Works great, thx!
    – Sean
    Sep 28, 2018 at 20:49

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.