If I understand well, you need to retrieve all the posts having category ‘y’ and also meta key ‘facebook_shares’, after that, you need the position of a specific post.
My proposal is:
- Create a class that extends
WP_Query
and filter the results for required category and meta field. This class should also filter the SQL query to set the meta value as a property of post objects returned byWP_Query::get_posts
- an Spl ArrayIterator to iterate all posts and put every post inside a
- SplHeap that implements
compare
method looking at meta properties of post objects. SplHeap has the propery of automatic order elements passed inserted in it. - a third class that make use of previous 2 by composition, to get the order of wanted post.
And only one db query is done.
Let’s code.
First of all write our SplHeap
implementation:
class SharesHeap extends SplHeap {
public function compare( $post1, $post2 ) {
$key = FB_Shares_Query::$key;
if ( ! $post1 instanceof WP_Post || ! isset( $post1->$key ) ) return 0;
if ( ! $post2 instanceof WP_Post || ! isset( $post2->$key ) ) return 0;
return (int)$post2->$key - (int) $post1->$key;
}
}
Now the class that extends WP_Query
to get the posts. Constructor of this class accept a category term, that can be passed as category id, a category slug or even a category object.
By default the 'post_type'
argument is set to ‘post’, but that can be overriden passing a second argument to constructor.
class FB_Shares_Query extends WP_Query {
public static $key = 'facebook_shares'; // change the metakey here, if needed
protected static $args = array( 'nopaging' => TRUE ); // get all posts
public function __construct( $cat = NULL, $post_type="post" ) {
if ( is_object($cat) && isset($cat->term_id) ) $cat = $cat->term_id;
$cat_field = is_numeric($cat) ? 'cat' : 'category_name';
self::$args['post_type'] = ! empty( $post_type ) ? $post_type : 'post';
self::$args[$cat_field] = $cat;
parent::__construct( self::$args );
}
public function get_posts() {
add_filter('posts_clauses', array(__CLASS__, 'my_filters') );
$results = parent::get_posts();
remove_filter('posts_clauses', array(__CLASS__, 'my_filters') );
return $results;
}
public static function my_filters( $pieces ) {
$key = self::$key;
$meta = $GLOBALS['wpdb']->postmeta;
$posts = $GLOBALS['wpdb']->posts;
$pieces['fields'] .= ", {$meta}.meta_value as {$key}";
$pieces['join'] .= "INNER JOIN {$meta} ON ({$posts}.ID = {$meta}.post_id)";
$where = " AND ( {$meta}.meta_key = '{$key}' AND ";
$where .= " CAST( {$meta}.meta_value AS SIGNED ) > 0 )";
$pieces['where'] .= $where;
return $pieces;
}
}
Then the class that “glues” previous:
class FB_Shares_Order {
protected $heap;
protected $posts;
public function __construct( SplHeap $heap ) {
$this->heap = $heap;
}
public function getOrder( WP_Post $post = NULL ) {
if ( ! $this->posts instanceof ArrayIterator ) return -1;
if ( ! isset( $post->ID ) ) return FALSE;
while( $this->posts->valid() ) {
$this->heap->insert( $this->posts->current() );
$this->posts->next();
}
$position = 0;
while( $this->heap->valid() ) {
$current = $this->heap->extract();
if ( $current->ID == $post->ID ) return $position + 1;
$position++;
}
return FALSE;
}
public function getPosts( WP_Query $query ) {
if ( $query->post_count > 0) $this->posts = new ArrayIterator( $query->posts );
}
}
Finally, a function that work as façade to the 3 classes. It accepts a post (id or object) and a category (id, slug or object):
function getFB_Shares_Order( $post, $cat ) {
if ( is_object($post) && isset( $post->ID ) ) $post = $post->ID;
if ( ! is_numeric($post) || ! (int) $post > 0 ) return FALSE;
if ( ! has_category($cat, $post) ) return FALSE;
$order = new FB_Shares_Order( new SharesHeap );
$order->getPosts( new FB_Shares_Query( $cat ) );
return $order->getOrder( get_post( $post ) );
}
Usage Example
global $post;
$position = getFB_Shares_Order( $post, 'uncategorized' );
Returning values:
If the query return some posts and the post given is one of them, then the function returns the post order in the post collection, looking at ‘facebook_shares’ meta key.
E.G. the query returns 3 posts, having values of ‘facebook_shares’ respectively: 10, 15, 30
and you pass to function the id of post that has ‘facebook_shares’ = 15, then the function return 2, because the post is the second in order.
If the query returns no posts, then the function return -1.
If the query returns some posts, but the wanted posts is not present among them, the function return false.
Caveats
There are some issues on core WP_Query using meta_query without specific values (‘compare’ = EXIST or NOT EXIST) specially if the query contains also a tax query.
To avoid them, in the class that extends WP query use some filters on SQL query, and where clause is setted “manually”. However, if there are some external code (plugin, themes) adding custom filters on query (using posts_where, posts_join and similar filter hooks) that code can break the call behaviour.
The code is very quicky tested, and seems to work, however.. no warranty.