Multiple Permalinks for Same Post

I’d like to be able to have multiple permalinks for the same underlying post (for i18n reasons) – eg

http://www.example.com/my-custom-post-type/this-is-a-cool-article
http://www.example.com/mon-type-de-poste-personnalise/cest-un-article-sympa

Both pointing to the same post page. Built in WP i18n functions would deal with the language switching on the post page itself. I’m more concerned with the URL display for both SEO and end user aesthetics.

I can’t use a 301 redirect as I don’t want the browser URL changing. I’m understand how to internationalize the custom post type slug, but not the actual post slug.

I’m thinking I can save the secondary post slug in a post meta field, but I’d have to hook on somewhere in the routing to enable it – I haven’t had much luck in finding the correct place to hook? Is there a place to hook into or an easier way to deal with this?

1 Answer
1

The request parsing is handled in WP::parse_request(). After that, there’s a action hook parse_request which gives you the instance of the wp object.

We assume that http://www.example.com/my-custom-post-type/this-is-a-cool-article is your permalink and http://www.example.com/mon-type-de-poste-personnalise/cest-un-article-sympa ends up in a 404. So the first thing to check in your callback should be, if the wp object is in a error/404 state:

if ( ! empty( $wp->query_vars[ 'error' ] && 404 == $wp->query_vars[ 'error' ] ) {
    //…
}

Edit: i made a mistake: it’s not sure, that $wp status is always 404 on your alias URL. It strongly depends on the rewrite rules on your system. So you need to check for your alias cpt slug directly.

Now you have to parse the request on your own and lookup for your meta value to find the corresponding post:

if ( ! empty( $wp->query_vars[ 'error' ] && '404' == $wp->query_vars[ 'error' ] ) {

    //look up for your post, this var should looks like 
    // 'mon-type-de-poste-personnalise/cest-un-article-sympa' in your case:
    $wp->request;
    //e.g.
    $parts = explode( "https://wordpress.stackexchange.com/", $wp->request );
    // your language slug
    $slug = ( isset( $parts[ 1 ] ) ) ? $parts[ 1 ] : '';
    // do your query with your meta value $slug here

}

Next thing is, to setup the $wp->query_vars as they would look like when we would facing the original request:

if ( ! empty( $wp->query_vars[ 'error' ] && '404' == $wp->query_vars[ 'error' ] ) {

    //look up for your post, this var should looks like 
    // 'mon-type-de-poste-personnalise/cest-un-article-sympa' in your case:
    $wp->request;
    //e.g.
    $parts = explode( "https://wordpress.stackexchange.com/", $wp->request );
    // your i18n post slug
    $slug = ( isset( $parts[ 1 ] ) ) ? $parts[ 1 ] : '';
    // do your query with your meta value $slug here
    // and setup the WP_Post object for the matching post
    $matching_post; // WP_Post

    // if you don't find any post, return here!

    // unset the error flag
    unset( $wp->query_vars[ 'error' ] );

    // your cpt slug
    $cpt_slug = 'my-custom-post-type';

    $wp->query_vars[ $cpt_slug ] = $matching_post->post_name;
    $wp->query_vars[ 'post_type' ] = $cpt_slug;
    $wp->query_vars[ 'name' ] = $matching_post->post_name;
}

The last thing you have to do is to prevent WordPress to redirect to the canonical URL automatically by removing the function redirect_canonical from the template_redirect action:

if ( ! empty( $wp->query_vars[ 'error' ] && '404' == $wp->query_vars[ 'error' ] ) {

    //look up for your post, this var should looks like 
    // 'mon-type-de-poste-personnalise/cest-un-article-sympa' in your case:
    $wp->request;
    //e.g.
    $parts = explode( "https://wordpress.stackexchange.com/", $wp->request );
    // your language slug
    $slug = ( isset( $parts[ 1 ] ) ) ? $parts[ 1 ] : '';
    // do your query with your meta value $slug here
    // and setup the WP_Post object for the matching post
    $matching_post; // WP_Post

    # unset the error flag
    unset( $wp->query_vars[ 'error' ] );

    # your cpt slug
    $cpt_slug = 'my-custom-post-type';

    $wp->query_vars[ $cpt_slug ] = $matching_post->post_name;
    $wp->query_vars[ 'post_type' ] = $cpt_slug;
    $wp->query_vars[ 'name' ] = $matching_post->post_name;

    # don't redirect to the canonical url
    remove_action( 'template_redirect', 'redirect_canonical' );
}

Put the last codeblock in a function and apply it to the parse_request action:

add_action( 'parse_request', 'wpse_126309_parse_request' );

/**
 * @wp-hook parse_request
 * @param WP $wp
 * @return void
 */
function wpse_126309_parse_request( $wp ) {
    // the code goes here
}

You should test this example (it’s not more than a proof of concept) for side effects.

Leave a Comment