In WordPress, as you should already know, when using get_posts() or query_posts() or even WP_Query , it is not possible to order the returned posts by specifying the list of post IDs in the order we want.
Instead, we need to iterate over the results and reorder them on the PHP side. These are performance hits and bad practice. Instead, we should use the MySQL built-in functions to retrieve messages in the correct order up.
Fortunately, there is posts_orderby that can be used to specify a custom ORDERBY statement, for example:
// 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, the problem with the above code is that it will affect the order of all requests on the page! Including queries made using plugins, shortcodes, etc.
Simple fix!
An easy way to fix this is to apply the filter only once and remove it as soon as it is called by placing remove_filter() inside the filter itself, so it runs 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);
Since I set this filter immediately before my user request, after executing my user request, it should be filtered using the posts_orderby filter set above, which is immediately disabled, so it will not affect future requests.
In theory, this is great, and it works great in most scenarios!
Problem with WPML
However, I came across a situation when using the WPML plugin, where this filter affects other requests than mine and causes errors. I believe that the WPML plugin creates its own request, which is executed immediately before my own custom request, which makes my filter apply to a WPML request instead of mine!
Is there a way to add validation to a filter to make sure that it affects the correct request?
Many thanks
Edit:
Fix for WPML
For information, while the accepted answer to this question is correct, it did not solve the problem that I encountered WPML. This 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);