0

The site http://www.bikefun.org is live and working, but the permalinks are not quite right. It is an event calendar site. Events are stored as a custom post type "bf_events", with dates stored as integer postmeta values - integer as produced by strtotime().

I'm using a permalink structure /events/2014/09/cyclewise-confidence-training-intermediate-2/ where the year and month are those of the event date, not the publication date of the post. This is sort of working, but in practice only the event title is being used by WP, and the number "2" at the end has been added by WP to keep the permalink unique. If you alter the year or month, the same page loads, whereas you should get a 404.

Here is the code:

function event_rewrite_rules() {
    global $wp_rewrite;

    $wp_rewrite->add_rewrite_tag( '%eventyear%', '([0-9]{4})', 'bf_events_year=');
    $wp_rewrite->add_rewrite_tag( '%eventmonth%', '([0-9]{2})', 'bf_events_month=');
    // $wp_rewrite->add_permastruct('bf_events', '/events/%bf_events_year%/%bf_events_month%/%postname%', false);
    add_rewrite_rule(  '^events/([0-9]{4})/([0-9]{2})/([^/]+)?', 'index.php??post_type=bf_events&pagename=$matches[2]', 'top' );
}
add_action('init', 'event_rewrite_rules');

I commented the add_permastruct call because it broke everything.

add_filter('post_type_link', 'event_permalink', 10, 4);

function event_permalink($permalink, $post, $leavename) {
    if ( get_post_type( $post ) === "bf_events" ) {
        $sd = get_post_meta( $post->ID, 'bf_events_startdate', true);
        $year = date('Y', $sd + get_option( 'gmt_offset' ) * 3600);
        $month = date('m', $sd + get_option( 'gmt_offset' ) * 3600);

        $rewritecode = array(
            '%eventyear%',
            '%eventmonth%',
            $leavename? '' : '%postname%',
        );

        $rewritereplace = array(
            $year,
            $month,
            $post->post_name
        );

        $permalink = str_replace($rewritecode, $rewritereplace, $permalink);
    }
    return $permalink;
}

To make it possible for WP to use year and month in the permalink, these are stored as additional postmeta values in the meta-save function:

update_post_meta($post->ID, "bf_events_startdate", $updatestartd );
update_post_meta($post->ID, "bf_events_year", date("Y", $updatestartd ) );
update_post_meta($post->ID, "bf_events_month", date("m", $updatestartd ) );

edit: I've been comparing my code to the plugin "The Events Calendar". This plugin inserts a date (different format to me) as part of the last component of the permalink, hyphenated with the event title: cultural-center-art-exhibition-2014-11-08 and notice that the same thing occurs there if you create a 2nd post with the same name, WP will append -2 to the title part of the permalink. However their code is better than mine in that if you change the date part of the permalink you get a 404, whereas mine will load the event so long as the title is correct, and you have anything matching /([0-9]{4})/([0-9]{2})/ in the date part of the permalink. I'll try and figure out how they are getting that 404 and might learn more.

1 Answer 1

1

The short answer: you need to modify the query for your single event post to include the metadata, so only posts with matching name, year, and month will be returned.

I'll show you the complete code I used to test this. I made a few changes to your code and simplified a few things, so be careful that all functions are updated.

First, the rewrite tags used in permalinks. No need to modify globals to add these, there's a function.

function wpd_event_rewrite_tags() {
    add_rewrite_tag( '%bf_events_year%', '([0-9]{4})' );
    add_rewrite_tag( '%bf_events_month%', '([0-9]{2})' );
}
add_action( 'init', 'wpd_event_rewrite_tags' );

Next is registering the post type. This is where we can set our permalink structure without needing to add rules manually. Note the use of rewrite tags in the event slug. I've otherwise simplified this down to the basics for a working example.

function wpd_event_post_type() {
    $args = array(
        'public' => true,
        'label'  => 'Events',
        'rewrite' => array( 'slug' => 'events/%bf_events_year%/%bf_events_month%' ),
        'supports' => array( 'title', 'editor', 'custom-fields' )
    );
    register_post_type( 'bf_events', $args );
}
add_action( 'init', 'wpd_event_post_type' );

Your post_type_link function remains largely the same, just with updated rewrite tags:

function wpd_event_permalink($permalink, $post, $leavename) {
    if ( get_post_type( $post ) === "bf_events" ) {
        $sd = get_post_meta( $post->ID, 'bf_events_startdate', true);
        $year = date('Y', $sd + get_option( 'gmt_offset' ) * 3600);
        $month = date('m', $sd + get_option( 'gmt_offset' ) * 3600);

        $rewritecode = array(
            '%bf_events_year%',
            '%bf_events_month%',
            $leavename ? '' : '%postname%',
        );

        $rewritereplace = array(
            $year,
            $month,
            $post->post_name
        );

        $permalink = str_replace($rewritecode, $rewritereplace, $permalink);
    }
    return $permalink;
}
add_filter( 'post_type_link', 'wpd_event_permalink', 10, 4 );

The final piece of the puzzle is where the magic happens. Here we add the month and year query vars into a meta query. Adding this also kicks in the canonical redirect, so it redirects to the correct year/month instead of returning a 404.

function wpd_single_event_queries( $query ){
    if( $query->is_singular()
        && $query->is_main_query()
        && isset( $query->query_vars['bf_events'] ) ){
            $meta_query = array(
                array(
                    'key'     => 'bf_events_year',
                    'value'   => $query->query_vars['bf_events_year'],
                    'compare' => '=',
                    'type'    => 'numeric',
                ),
                array(
                    'key'     => 'bf_events_month',
                    'value'   => $query->query_vars['bf_events_month'],
                    'compare' => '=',
                    'type'    => 'numeric',
                ),
            );
            $query->set( 'meta_query', $meta_query );
    }
}
add_action( 'pre_get_posts', 'wpd_single_event_queries' );
3
  • I've done as @Milo suggests and it works. Two things are still puzzling me: 1. The documentation on meta_query doesn't mention "canonical" nor "redirect" so I can't figure out why it redirects 2. I think this confirms that it's not possible to prevent WP from adding a numeral to the end of the post title where a post is published with a duplicated title. My original idea was that there could be two posts with the same title but differing in one of the meta_query vars and their URLs would be identical except for the meta_value.
    – Nik Dow
    Apr 8, 2015 at 21:19
  • @NikDow did you ever find a solution for removing the number at the end of the url even if it is the same title? I have the exact same problem.
    – Snowball
    Sep 21, 2017 at 10:08
  • No I didn't get any further with this. WP adds a number to the text part of the URL to prevent duplicates, and it redirects an incorrect /year/month to the correct year/month based on that text component.
    – Nik Dow
    Sep 24, 2017 at 1:40

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.