Skip to content

Instantly share code, notes, and snippets.

@jameswburke
Created December 5, 2016 20:06
Show Gist options
  • Save jameswburke/d0776d742ab74c469cd8af8dacd916fc to your computer and use it in GitHub Desktop.
Save jameswburke/d0776d742ab74c469cd8af8dacd916fc to your computer and use it in GitHub Desktop.
Unique_WP_Query.php
<?php
/**
* Wrapper for WP_Query
* Interacts directly with WP_Query_Manager to ensure
* only unique posts are ever returned.
*
* Known issues:
* Won't work with offset values
*/
class Unique_WP_Query extends WP_Query {
function __construct( $args ) {
// Act as a flag for pre_get_posts
$args['unique_wp_query'] = true;
// Initialize the WP_Query object like normal
parent::__construct( $args );
// Track used posts, and remove duplicates
Unique_WP_Query_Manager::process_posts( $this->posts, $this->post_count, $this->get( 'original_posts_per_page' ) );
}
}
/**
* Keeps track of which posts have already appeared and
* removes them from future Unique_WP_Query objects.
*/
class Unique_WP_Query_Manager {
/**
* Keep track of which post_ids have already been used
* @var array
*/
public static $used_post_ids = array();
/**
* Keeps track of how many used_posts_ids exist
* @var integer
*/
public static $used_post_count = 0;
/**
* Takes the posts and post count of a WP_Query object and
* ensures that it removes posts that have already been used,
* and then trims it to the necessary size.
*
* @param array &$posts
* @param integer &$post_count
*/
public static function process_posts( &$posts, &$post_count, $original_posts_per_page ) {
// Keep track of how many posts are acceptable to return
$current_accepted_post_count = 0;
// Make sure we have posts
if ( empty( (array) $posts ) ) {
return;
}
// Loop through all the found posts
foreach ( (array) $posts as $key => $post ) {
// If we have enough acceptable posts, break the loop
// Otherwise, determine if we've already used this post
if ( $original_posts_per_page > $current_accepted_post_count ) {
// Has this post already been used?
if ( in_array( $post->ID, Unique_WP_Query_Manager::$used_post_ids ) ) {
// Remove from $posts
unset( $posts[ $key ] );
// And update count
$post_count--;
} else {
// If not, add it to the list of used ids
Unique_WP_Query_Manager::$used_post_ids[] = $post->ID;
// Update how many accepted posts we have
$current_accepted_post_count++;
}
} else {
// If we have enough acceptable posts, break the foreach
// This prevents extra results from accidently being added to $used_post_ids
break;
}
}
// Reindex the array correctly
$posts = array_values( $posts );
// Update the $used_post_count
Unique_WP_Query_Manager::$used_post_count = count( Unique_WP_Query_Manager::$used_post_ids );
// Trim any excess posts and update $post_count
if ( count( $posts ) > $original_posts_per_page ) {
// Remove extra posts
$posts = array_slice( $posts, 0, $original_posts_per_page );
// Update post count to our new value
$post_count = $original_posts_per_page;
}
}
}
if ( ! function_exists( 'unique_wp_query_pre_get_posts' ) ) {
/**
* Increase the posts_per_page value by the number of posts that have
* already been used. Executes as the last pre_get_posts action.
*/
function unique_wp_query_pre_get_posts( &$query ) {
// Only apply to Unique_WP_Query objects
if ( true === $query->get( 'unique_wp_query' ) ) {
// Check our upward bound of posts_per_page
$posts_per_page = $query->get( 'posts_per_page' );
if ( $posts_per_page <= 200 ) {
// Increase posts_per_page by the amount of used post_ids
$query->set( 'original_posts_per_page', $posts_per_page );
$query->set( 'posts_per_page', $posts_per_page + Unique_WP_Query_Manager::$used_post_count );
} else {
// Max out at 200 posts_per_page
$query->set( 'original_posts_per_page', 200 );
$query->set( 'posts_per_page', 200 );
}
}
}
// Ensure it's always the last pre_get_posts action
add_action( 'pre_get_posts', 'unique_wp_query_pre_get_posts', PHP_INT_MAX );
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment