In WordPress as you must already known, when using get_posts() or query_posts() or even WP_Query, it is not possible to order the returned posts by specifying a list of post ID in the order we want.

Instead we have to loop through the results and re-order them on the PHP side. This is a performance hit and a bad practice. Instead we should use built-in MySQL functions to retrieve the posts in the desired order upfront.

Thankfully there is the posts_orderby which can be used to specify a custom ORDERBY statement, like this:

// My list of post IDs in my custom order
$my_post_ids = array(1,3,2);

// Apply filter to the ORDERBY SQL statement
add_filter('posts_orderby', 'my_custom_orderby');
function my_custom_orderby($orderby_statement) {
    global $my_post_ids;
    $orderby_statement = 'FIELD(ID, '.implode(',',$my_post_ids).')';     
    return $orderby_statement;
}

// My custom query
$my_custom_query = new WP_Query(array('post_type' => 'post', 'post__in' => $my_post_ids);

However there is a problem with the above code, is that it will affect the order of all queries on the page! Including queries made by plugins, shortcodes, and so on.

Easy fix!

The simple way to fix this, is to apply the filter only one time, and remove it as soon as it is called, by putting a remove_filter() within the filter itself, so it is run only once:

// My list of post IDs in my custom order
$my_post_ids = array(1,3,2);

// Apply filter to the ORDERBY SQL statement
add_filter('posts_orderby', 'my_custom_orderby');
function my_custom_orderby($orderby_statement) {

    // Disable this filter for future queries!
    remove_filter(current_filter(), __FUNCTION__);

    global $my_post_ids;
    $orderby_statement = 'FIELD(ID, '.implode(',',$my_post_ids).')';     
    return $orderby_statement;
}

// My custom query
$my_custom_query = new WP_Query(array('post_type' => 'post', 'post__in' => $my_post_ids);

Because I set this filter just before my custom query, once I execute my custom query it should be filtered by the posts_orderby filter set above, which is then immediately disabled so it won't affect any future queries.

In theory, that's great, and it works great in most case scenarios!

An issue with WPML

However I have encountered a case when using the WPML plugin where this filter affects other queries than mine and causes errors. I believe the WPML plugin is creating a query of its own that is executed just before my own custom query, making my filter applies to the WPML query instead of mine!

Is there any possible way to add a check within the filter to make sure that it affects the correct query?

Thank you very much


Edit:

The fix for WPML

For information, while the accepted answer for this question is correct, it didn't solve the problem I was having with WPML. Here is how I fixed the WPML conflict:

// My list of post IDs in my custom order
$my_post_ids = array(1,3,2);

// Apply filter to the ORDERBY SQL statement
add_filter('posts_orderby', 'my_custom_orderby');
function my_custom_orderby($orderby_statement) {

    // Disable this filter for future queries!
    remove_filter(current_filter(), __FUNCTION__);

    global $my_post_ids, $wpdb;
    $orderby_statement = 'FIELD('.$wpdb->base_prefix.'posts.ID, '.implode(',',$my_post_ids).')';     
    return $orderby_statement;
}

// My custom query
$my_custom_query = new WP_Query(array('post_type' => 'post', 'post__in' => $my_post_ids);
有帮助吗?

解决方案

This filter takes two parameters, $orderby and &$this. "this" being the WP_Query object. I'm not sure how to detect that WPML is making the call, but we can check that your call is the one being made.

$my_post_ids = array(1,3,2);

add_filter( 'posts_orderby', 'my_custom_orderby', 10, 2 );

function my_custom_orderby( $orderby_statement, $object ) 
{
    global $my_post_ids;
    if( $my_post_ids != $object->query['post__in'] )
        return $orderby_statement;

    // Disable this filter for future queries!
    remove_filter( current_filter(), __FUNCTION__ );

    $orderby_statement = 'FIELD(ID, ' . implode( ',', $my_post_ids ) . ')';     
    return $orderby_statement;
}
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top