将帖子分配给特定类别等同于将其分配给所有类别

时间:2013-11-01 作者:its_me

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?

2 个回复
最合适的回答,由SO网友:its_me 整理而成

解决此问题的一种方法是使用redirect_canonical 筛选以取消对分配给国际(intl)类别的帖子的重定向(当帖子分配给的类别与请求的帖子URL中的类别slug不匹配时会发生这种情况)。

/*
 * 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;
}
该功能可能不完美,在这种情况下,欢迎任何更正!

感谢@Zogot forthe idea, 和@StephenHarris forthe ideaall the help.

SO网友:Zogot

也许最简单的解决办法是save_post 如果设置了一个特殊的$\\u POST变量(比如从复选框中),则为其分配在版本分类法中找到的所有标记。

例如:

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.
}
这样,您就不再需要修改查询了,应该可以获得扩展术语列表的所有好处。

结束