Take the 2-minute tour ×
WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. It's 100% free, no registration required.

Allow me to explain with an example, for the sake of clarity. Please take the time to read it all to get a good idea of what I am exactly after.

On my WordPress site categories are used as Editions and there are 4 of them — Category (slug):

  • US (main)
  • UK (uk)
  • India (in)
  • International (intl)

The permalink structure is /%category%/%year%/%monthnum%/%day%/%postname%/. With this permalink structure in place, a post assigned to more than 1 category will be accessible via any of the category slugs in the permalink.

For example, if a post is assigned to US and UK categories, it'll be accessible via both of these URLs (no redirection):

http://example.com/main/2013/11/01/sample-post/
http://example.com/uk/2013/11/01/sample-post/

Points of note:

  1. The permalink will still only include the slug of the category with the lowest ID.

  2. If you try using the slug of a category that the post is not assigned to, it'll redirect back to the permalink. For instance, these two (note intl and in):

    http://example.com/intl/2013/11/01/sample-post/
    http://example.com/in/2013/11/01/sample-post/
    

    Will redirect back to the permalink, i.e.

    http://example.com/main/2013/11/01/sample-post/
    

The problem: For those posts that I want to be shown in all Editions (categories in my case), I can check all categories when publishing the post. But that'd cause problems later on when I add more editions, i.e. the old posts won't be shown under the new Editions/categories.

So I use this custom function to suit my needs:

add_filter('pre_get_posts','better_editions_archive');
function better_editions_archive( $query ) {
    if ( $query->is_tax( 'category' ) && $query->is_main_query() ) {
        $query->set( 'post_type', array( 'post' ) );
        $query->set( 'tax_query',
            array(
                'relation' => 'OR',
                array(
                    'taxonomy' => 'category',
                    'field' => 'slug',
                    'terms' => 'intl',
                    'operator' => 'IN'
                )
            )
        );
    }
    return $query;
}

/*
 * Based on:
 * http://wordpress.stackexchange.com/a/90573/10691
 */

With that function in place, when I want a post to be displayed in all Editions (categories), I can simply assign the post to International (intl) category and voila!

The problem is, now the post is only accessible via its permalink i.e. http://example.com/intl/2013/11/01/sample-post/.

But I want it to be accessible (with no redirection) via all the category slugs, i.e.

http://example.com/main/2013/11/01/sample-post/
http://example.com/uk/2013/11/01/sample-post/
http://example.com/in/2013/11/01/sample-post/

Reason: In each Edition (category) archive, I want all the posts' links to start with that specific category's slug i.e. if the archive URL is http://example.com/uk/, I made sure that all post URLs'd look like this:

http://example.com/uk/2013/11/01/sample-post-1/
http://example.com/uk/2013/11/01/sample-post-2/
http://example.com/uk/2013/11/01/sample-post-3/

I used str_replace() for this, just so it's clear.


Now, the question is, when a post is assigned to International (intl) category, how can I make sure that it's accessible, without any redirection whatsoever, via all category slugs, as if the post were assigned to all the categories?

Precisely, how do I make assigning a post to a specific category equivalent to assigning it to all categories?

share|improve this question
    
Is this during development still or something that needs to be applied to an existing set of posts? –  Zogot Nov 1 '13 at 13:46
    
@Zogot At this time, the site's under development, but I don't plan to launch it until all the primary features I have in mind are in place. –  its_me Nov 1 '13 at 14:08
    
if a post is available in multiple locations, what is the canonical URL of a post? the googles will not like finding largely the same content under different URLs, and will penalize you for doing so. –  Milo Nov 1 '13 at 17:00
    
@Milo I have planned to add the canonical meta tag pointing to the right URL so that search engines know which one to index. Let's not worry about SEO. I've all that taken care of sensibly i.e. don't confuse the users, don't confuse the bots. :) Thanks for the heads up! –  its_me Nov 1 '13 at 17:12
add comment

2 Answers 2

up vote 1 down vote accepted

One way to deal with the problem is to use the redirect_canonical filter to cancel the redirect (that happens when the category that the post is assigned to doesn't match the category slug in the requested post URL) for posts assigned to International (intl) category.

/*
 * Related core file: wp-includes/canonical.php
 *
 * NOTE: If you see no change after adding the function, try re-saving the permalink settings.
 */

add_filter( 'redirect_canonical', 'itsme_redirect_canonical', 10, 2 );
function itsme_redirect_canonical( $redirect_url, $requested_url ) {
    global $wp_rewrite;

    if( is_single() && in_category( 'intl' ) && strpos( $wp_rewrite->permalink_structure, '%category%' ) !== false && $cat = get_query_var( 'category_name' ) ) {

        $category = get_category_by_path( $cat );

        if( $category && !is_wp_error( $category ) ) {

            return str_replace( 'intl', $cat, $redirect_url );

            // return $requested_url;

            /*
             * WHY NOT SIMPLY `return $requested_url;`?
             *
             * By returning $requested_url you're returning something that might not be
             * canonical (e.g. extra slashes, etc) and there's a possibility that the
             * safety rules that WordPress has in place will redirect your URL back to
             * the (original) canonical URL.
             *
             * For example, this will work fine:
             *
             *  http://example.com/in/2013/11/02/sample-post/
             *
             * But this:
             *
             *  http://example.com/in/2013/11/02/sample-post/////
             *
             * Will redirect back to the original, canonical URL, i.e.,
             *
             *  http://example.com/intl/2013/11/02/sample-post/
             *
             * In order to avoid that, you'll additionally have to write some safety
             * rules so that a *corrected* URL, rather than simply the requested one, is
             * returned, in which case, you may need to use `str_replace` or `preg_replace`
             * to remove the extra characters, among other rules needed, if any. Now that
             * can get complex.
             *
             * So instead of dealing with all this mess ourselves, we are allowing
             * WordPress to redirect to the (original) canonical URL, and at that point,
             * we are replacing 'intl' (which is the category slug in the canonical URL)
             * with the category slug in the requested URL.
             */

        }

    }

    return $redirect_url;
}

The function may not be perfect, in which case any corrections are welcome!

Thanks to @Zogot for the idea, and @StephenHarris for the idea and all the help.

share|improve this answer
add comment

Perhaps the easiest solution is to hook into save_post and if the a special $_POST variable is set ( say from a checkbox ) assign it all tags found in your Edition taxonomy.

An example could be the following:

add_action( 'save_post', 'assign_edition_terms' );

function assign_edition_terms( $post_id ) {
    if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
            return;

    if ( ! current_user_can( 'edit_post', $post_id ) )
        return;

    // Could also check on post type here.

    if ( ! filter_has_var( INPUT_POST, 'assign_international' ) )
        return;

    // Get all international terms
    $editions = get_terms( 'edition_taxonomy', array(
        'parent' => 0, // only return parent terms
        'hide_empty' => false // get also terms that have no content yet.
    ) );

    $edition_ids = array();
    foreach ( $editions as $edition ) {
        $edition_ids[] = $edition->term_id;
    }

    // Assign all the terms
    wp_set_post_terms( $post_id, $edition_ids, 'edition_taxonomy' );

    // You may require a rewrite flush here.
}

Then you no longer require the need to alter the query, and should get all the benefits of an expanding term list.

share|improve this answer
    
But what happens when I add new categories? This isn't going to work retrospectively, does it? –  its_me Nov 1 '13 at 14:10
    
ah no, you are right. A solution could be made to solve that, that extends this. Perhaps better is to find some solution with the rewrite rules –  Zogot Nov 1 '13 at 14:12
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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