1+ <?php
2+ /**
3+ * @package Unique_WP_Query
4+ * @version 1.0.0
5+ */
6+ /*
7+ Plugin Name: Unique WP Query
8+ Plugin URI: https://github.com/firstandthird/unique_wp_query
9+ Description: A plugin for doing unique WP_Query
10+ Author: firstandthird
11+ Version: 1.0.0
12+ */
13+ // adapted & taken from from https://gist.github.com/jameswburke/d0776d742ab74c469cd8af8dacd916fc
14+
15+ /**
16+ * Wrapper for WP_Query
17+ * Interacts directly with WP_Query_Manager to ensure
18+ * only unique posts are ever returned.
19+ *
20+ * Known issues:
21+ * Won't work with offset values
22+ */
23+ class Unique_WP_Query extends WP_Query {
24+ function __construct ($ args ) {
25+ // Act as a flag for pre_get_posts
26+ $ args ['unique_wp_query ' ] = true ;
27+
28+ // Initialize the WP_Query object like normal
29+ parent ::__construct ($ args );
30+
31+ // Track used posts, and remove duplicates
32+ Unique_WP_Query_Manager::process_posts ($ this ->posts , $ this ->post_count , $ this ->get ('original_posts_per_page ' ));
33+ }
34+ }
35+
36+ /**
37+ * Keeps track of which posts have already appeared and
38+ * removes them from future Unique_WP_Query objects.
39+ */
40+ class Unique_WP_Query_Manager {
41+
42+ /**
43+ * Keep track of which post_ids have already been used
44+ * @var array
45+ */
46+ public static $ used_post_ids = [];
47+
48+ /**
49+ * Keeps track of how many used_posts_ids exist
50+ * @var integer
51+ */
52+ public static $ used_post_count = 0 ;
53+
54+ /**
55+ * Helper to make sure no duplicate ids are added
56+ * useful for content outside of the normal query
57+ */
58+ public static function add_post_id ($ id ) {
59+ if (!in_array ($ id , Unique_WP_Query_Manager::$ used_post_ids , true )) {
60+ Unique_WP_Query_Manager::$ used_post_ids [] = $ id ;
61+ Unique_WP_Query_Manager::$ used_post_count = count (Unique_WP_Query_Manager::$ used_post_ids );
62+ }
63+ }
64+
65+ /**
66+ * Takes the posts and post count of a WP_Query object and
67+ * ensures that it removes posts that have already been used,
68+ * and then trims it to the necessary size.
69+ *
70+ * @param array &$posts
71+ * @param integer &$post_count
72+ */
73+ public static function process_posts (&$ posts , &$ post_count , $ original_posts_per_page ) {
74+
75+ // Keep track of how many posts are acceptable to return
76+ $ current_accepted_post_count = 0 ;
77+
78+ // Make sure we have posts
79+ if (empty ((array ) $ posts )) {
80+ return ;
81+ }
82+
83+ // Loop through all the found posts
84+ foreach ((array ) $ posts as $ key => $ post ) {
85+
86+ // If we have enough acceptable posts, break the loop
87+ // Otherwise, determine if we've already used this post
88+ if ($ original_posts_per_page > $ current_accepted_post_count ) {
89+
90+ // Has this post already been used?
91+ if (in_array ($ post ->ID , Unique_WP_Query_Manager::$ used_post_ids , true )) {
92+ // Remove from $posts
93+ unset($ posts [$ key ]);
94+
95+ // And update count
96+ $ post_count --;
97+ } else {
98+ // If not, add it to the list of used ids
99+ Unique_WP_Query_Manager::$ used_post_ids [] = $ post ->ID ;
100+
101+ // Update how many accepted posts we have
102+ $ current_accepted_post_count ++;
103+ }
104+ } else {
105+ // If we have enough acceptable posts, break the foreach
106+ // This prevents extra results from accidently being added to $used_post_ids
107+ break ;
108+ }
109+ }
110+
111+ // Reindex the array correctly
112+ $ posts = array_values ($ posts );
113+
114+ // Update the $used_post_count
115+ Unique_WP_Query_Manager::$ used_post_count = count (Unique_WP_Query_Manager::$ used_post_ids );
116+
117+ // Trim any excess posts and update $post_count
118+ if (count ($ posts ) > $ original_posts_per_page ) {
119+
120+ // Remove extra posts
121+ $ posts = array_slice ($ posts , 0 , $ original_posts_per_page );
122+
123+ // Update post count to our new value
124+ $ post_count = $ original_posts_per_page ;
125+ }
126+ }
127+ }
128+
129+ if (!function_exists ('unique_wp_query_pre_get_posts ' )) {
130+ /**
131+ * Increase the posts_per_page value by the number of posts that have
132+ * already been used. Executes as the last pre_get_posts action.
133+ */
134+ function unique_wp_query_pre_get_posts (&$ query ) {
135+ if (true === $ query ->get ('unique_wp_query ' )) {
136+ $ posts_per_page = $ query ->get ('posts_per_page ' );
137+
138+ // Increase posts_per_page by the amount of used post_ids
139+ $ query ->set ('original_posts_per_page ' , min ($ posts_per_page , 200 ));
140+ $ query ->set ('posts_per_page ' , min ($ posts_per_page + Unique_WP_Query_Manager::$ used_post_count , 200 ));
141+ }
142+ }
143+
144+ add_action ('pre_get_posts ' , 'unique_wp_query_pre_get_posts ' , PHP_INT_MAX );
145+ }
0 commit comments