PluginProbe
WebberZone Top 10 — Popular Posts / 4.3.4
WebberZone Top 10 — Popular Posts v4.3.4
4.5.1 4.5.0 4.4.3 4.4.2 4.4.1 4.4.0 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 trunk 1.0 1.0.1 1.1 1.2 1.3 1.4 1.4.1 1.5 1.5.1 1.5.2 1.5.3 1.6 1.6.1 All 117 releases
top-10 / includes / class-top-ten-core-query.php

class-top-ten-core-query.php in WebberZone Top 10 — Popular Posts 4.3.4, at includes/class-top-ten-core-query.php

955 lines 28.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Core Query class.
4 *
5 * @package WebberZone\Top_Ten
6 */
7
8 namespace WebberZone\Top_Ten;
9
10 use WebberZone\Top_Ten\Database;
11 use WebberZone\Top_Ten\Frontend\Display;
12 use WebberZone\Top_Ten\Util\Helpers;
13 use WebberZone\Top_Ten\Util\Hook_Registry;
14
15 // If this file is called directly, abort.
16 if ( ! defined( 'WPINC' ) ) {
17 die;
18 }
19
20 /**
21 * Query API: Top_Ten_Core_Query class.
22 *
23 * @since 4.0.0
24 */
25 class Top_Ten_Core_Query extends \WP_Query {
26
27 /**
28 * Array of blog IDs.
29 *
30 * @since 3.0.2
31 * @var int[]
32 */
33 public $blog_id;
34
35 /**
36 * Flag to indicate if multiple blogs are being queried.
37 *
38 * @since 3.2.0
39 * @var bool
40 */
41 public $multiple_blogs = false;
42
43 /**
44 * Cache set flag.
45 *
46 * @since 3.0.0
47 * @var bool
48 */
49 public $in_cache = false;
50
51 /**
52 * Daily flag.
53 *
54 * @since 3.0.0
55 * @var bool
56 */
57 public $is_daily = false;
58
59 /**
60 * Query vars, before parsing.
61 *
62 * @since 3.0.2
63 * @var array
64 */
65 public $input_query_args = array();
66
67 /**
68 * Query vars, after parsing.
69 *
70 * @since 3.0.0
71 * @var array
72 */
73 public $query_args = array();
74
75 /**
76 * Flag to indicate if this is a Top Ten query.
77 *
78 * @since 4.0.0
79 * @var bool
80 */
81 public $is_top_ten_query = true;
82
83 /**
84 * Top Ten table name being queried.
85 *
86 * @since 3.0.0
87 * @var string
88 */
89 public $table_name;
90
91 /**
92 * Stores the SELECT clauses in WordPress multisite.
93 *
94 * @since 3.0.0
95 * @var array
96 */
97 public $ms_select;
98
99 /**
100 * Random order flag.
101 *
102 * @since 3.3.0
103 * @var bool
104 */
105 public $random_order = false;
106
107 /**
108 * Cache name.
109 *
110 * @since 4.1.0
111 * @var string
112 */
113 protected $cache_name = '';
114
115 /**
116 * Main constructor.
117 *
118 * @since 3.0.0
119 *
120 * @param array|string $args The Query variables. Accepts an array or a query string.
121 */
122 public function __construct( $args = array() ) {
123 $this->prepare_query_args( $args );
124
125 if ( isset( $args['top_ten_query'] ) && true === $args['top_ten_query'] && ! $this->is_top_ten_query ) {
126 $this->hooks();
127 }
128 }
129
130 /**
131 * Set up hooks.
132 *
133 * @since 4.0.0
134 */
135 public function hooks() {
136 Hook_Registry::add_filter( 'pre_get_posts', array( $this, 'pre_get_posts' ), 10 );
137 Hook_Registry::add_filter( 'posts_fields', array( $this, 'posts_fields' ), 10, 2 );
138 Hook_Registry::add_filter( 'posts_join', array( $this, 'posts_join' ), 10, 2 );
139 Hook_Registry::add_filter( 'posts_where', array( $this, 'posts_where' ), 10, 2 );
140 Hook_Registry::add_filter( 'posts_orderby', array( $this, 'posts_orderby' ), 9999, 2 );
141 Hook_Registry::add_filter( 'posts_groupby', array( $this, 'posts_groupby' ), 10, 2 );
142 Hook_Registry::add_filter( 'posts_clauses', array( $this, 'posts_clauses' ), 20, 2 );
143 Hook_Registry::add_filter( 'posts_request', array( $this, 'posts_request' ), 20, 2 );
144 Hook_Registry::add_filter( 'posts_pre_query', array( $this, 'posts_pre_query' ), 10, 2 );
145 Hook_Registry::add_filter( 'the_posts', array( $this, 'the_posts' ), 10, 2 );
146 Hook_Registry::add_action( 'the_post', array( $this, 'switch_to_blog_in_loop' ) );
147 Hook_Registry::add_action( 'loop_end', array( $this, 'loop_end' ) );
148 }
149
150 /**
151 * Prepare the query variables.
152 *
153 * @since 3.0.0
154 * @see \WP_Query::parse_query()
155 * @see tptn_get_registered_settings()
156 *
157 * @param string|array $args {
158 * Optional. Array or string of Query parameters.
159 *
160 * @type array|string $blog_id An array or comma-separated string of blog IDs.
161 * @type bool $daily Set to true to get the daily/custom period posts. False for overall.
162 * @type array|string $include_cat_ids An array or comma-separated string of category/custom taxonomy term_taxonomy_ids.
163 * @type array|string $include_post_ids An array or comma-separated string of post IDs.
164 * @type int $offset Offset the related posts returned by this number.
165 * @type bool $strict_limit If this is set to false, then it will fetch 3x posts.
166 * }
167 */
168 public function prepare_query_args( $args = array() ) {
169
170 $tptn_settings = tptn_get_settings();
171
172 $defaults = array(
173 'blog_id' => get_current_blog_id(),
174 'daily' => false,
175 'include_cat_ids' => 0,
176 'include_post_ids' => 0,
177 'offset' => 0,
178 'strict_limit' => true,
179 );
180 $defaults = array_merge( $defaults, $tptn_settings );
181 $args = wp_parse_args( $args, $defaults );
182 $args = Helpers::parse_wp_query_arguments( $args );
183
184 // Set necessary variables.
185 $args['top_ten_query'] = true;
186 $args['suppress_filters'] = false;
187 $args['ignore_sticky_posts'] = true;
188 $args['no_found_rows'] = true;
189
190 // Store query args before we manipulate them.
191 $this->input_query_args = $args;
192 $this->is_top_ten_query = isset( $this->input_query_args['is_top_ten_query'] ) ? $this->input_query_args['is_top_ten_query'] : false;
193
194 $this->table_name = Database::get_table( $args['daily'] );
195 $this->is_daily = $args['daily'];
196
197 /**
198 * Applies filters to the query arguments before executing the query.
199 *
200 * This function allows developers to modify the query arguments before the query is executed.
201 *
202 * @since 4.0.0
203 *
204 * @param array $args The query arguments.
205 * @param \WP_Term|\WP_Post_Type|\WP_Post|\WP_User|null $queried_object The queried object.
206 */
207 $args = apply_filters( 'tptn_query_args_before', $args, get_queried_object() );
208
209 // Parse the blog_id argument to get an array of IDs.
210 $this->blog_id = wp_parse_id_list( $args['blog_id'] );
211 if ( is_multisite() ) {
212 if ( count( $this->blog_id ) > 1 || ( 1 === count( $this->blog_id ) && get_current_blog_id() !== (int) $this->blog_id[0] ) ) {
213 $this->multiple_blogs = true;
214 }
215 }
216
217 // Set the number of posts to be retrieved with over-fetch buffer for PHP exclusions.
218 $requested_limit = (int) $args['limit'];
219 if ( empty( $args['posts_per_page'] ) ) {
220 if ( $args['strict_limit'] ) {
221 // Calculate buffer size: min(limit * 1.5, limit + 20) capped at reasonable max.
222 $buffer_limit = min( (int) ( $requested_limit * 1.5 ), $requested_limit + 20 );
223 $buffer_limit = min( $buffer_limit, 100 ); // Cap at 100 to prevent over-fetching.
224
225 /**
226 * Filter the query buffer limit for PHP exclusions.
227 *
228 * Allows developers to adjust the buffer size when many posts might be excluded
229 * by PHP-side filtering to ensure enough posts are returned after exclusions.
230 *
231 * @since 4.2.0
232 *
233 * @param int $buffer_limit Buffer limit for posts_per_page.
234 * @param int $requested_limit Original requested limit.
235 * @param array $args Query arguments.
236 */
237 $buffer_limit = apply_filters( 'tptn_query_buffer_limit', $buffer_limit, $requested_limit, $args );
238
239 $args['posts_per_page'] = $buffer_limit;
240 } else {
241 $args['posts_per_page'] = $requested_limit * 3;
242 }
243 }
244
245 if ( empty( $args['post_type'] ) ) {
246
247 // If post_types is empty or contains a query string then use parse_str else consider it comma-separated.
248 if ( ! empty( $args['post_types'] ) && is_array( $args['post_types'] ) ) {
249 $post_types = $args['post_types'];
250 } elseif ( ! empty( $args['post_types'] ) && false === strpos( $args['post_types'], '=' ) ) {
251 $post_types = explode( ',', $args['post_types'] );
252 } else {
253 parse_str( $args['post_types'], $post_types ); // Save post types in $post_types variable.
254 }
255
256 // If post_types is empty or if we want all the post types.
257 if ( empty( $post_types ) || 'all' === $args['post_types'] ) {
258 $post_types = get_post_types(
259 array(
260 'public' => true,
261 )
262 );
263 }
264
265 /**
266 * Filter the post_types passed to the query.
267 *
268 * @since 2.2.0
269 * @since 3.0.0 Changed second argument from post ID to $args.
270 *
271 * @param array $post_types Array of post types to filter by.
272 * @param array $args Arguments array.
273 */
274 $args['post_type'] = apply_filters( 'top_ten_posts_post_types', $post_types, $args );
275 }
276
277 // Tax Query.
278 if ( ! empty( $args['tax_query'] ) && is_array( $args['tax_query'] ) ) {
279 $tax_query = $args['tax_query'];
280 } else {
281 $tax_query = array();
282 }
283
284 if ( ! empty( $args['include_cat_ids'] ) ) {
285
286 /**
287 * Filter to include the parent terms when including terms.
288 *
289 * @since 4.0.0
290 *
291 * @param bool|string $include_terms_include_parents False to exclude only the categories specified,
292 * 'parent' to include the parent categories and
293 * 'all' to include all the ancestors. Default is false.
294 */
295 $include_terms_include_parents = apply_filters( 'top_ten_query_include_terms_include_parents', false );
296
297 $include_terms_include_parents = $include_terms_include_parents ? wz_get_all_parent_ids( wp_parse_id_list( $args['include_cat_ids'] ), $include_terms_include_parents ) : wp_parse_id_list( $args['include_cat_ids'] );
298
299 $tax_query[] = array(
300 'field' => 'term_taxonomy_id',
301 'terms' => $include_terms_include_parents,
302 'include_children' => false,
303 );
304 }
305
306 if ( ! empty( $args['exclude_categories'] ) ) {
307
308 /**
309 * Filter to include the parent categories when excluding categories.
310 *
311 * @since 4.0.0
312 *
313 * @param bool|string $exclude_terms_include_parents False to exclude only the categories specified,
314 * 'parent' to include the parent categories and
315 * 'all' to include all the ancestors. Default is false.
316 */
317 $exclude_terms_include_parents = apply_filters( 'top_ten_query_exclude_terms_include_parents', false );
318
319 $exclude_categories = $exclude_terms_include_parents ? wz_get_all_parent_ids( wp_parse_id_list( $args['exclude_categories'] ), $exclude_terms_include_parents ) : wp_parse_id_list( $args['exclude_categories'] );
320
321 $tax_query[] = array(
322 'field' => 'term_taxonomy_id',
323 'terms' => $exclude_categories,
324 'operator' => 'NOT IN',
325 'include_children' => false,
326 );
327 }
328
329 /**
330 * Filter the tax_query passed to the query.
331 *
332 * @since 3.0.0
333 *
334 * @param array $tax_query Array of tax_query parameters.
335 * @param array $args Arguments array.
336 */
337 $tax_query = apply_filters( 'top_ten_query_tax_query', $tax_query, $args );
338
339 // Add a relation key if more than one $tax_query.
340 if ( count( $tax_query ) > 1 && ! isset( $tax_query['relation'] ) ) {
341 /**
342 * Filter the tax_query relation parameter.
343 *
344 * @since 3.2.0
345 *
346 * @param string $relation The logical relationship between each inner taxonomy array when there is more than one. Default is 'AND'.
347 * @param array $args Arguments array.
348 */
349 $tax_query['relation'] = apply_filters( 'top_ten_query_tax_query_relation', 'AND', $args );
350 }
351
352 $args['tax_query'] = $tax_query; // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
353
354 // Set date_query.
355 if ( ! empty( $args['date_query'] ) && is_array( $args['date_query'] ) ) {
356 // If date_query is already provided, use it as-is.
357 $date_query = $args['date_query'];
358 } else {
359 // Create date_query from how_old parameter.
360 $date_query = array(
361 array(
362 'after' => $args['how_old'] ? Helpers::get_from_date( null, $args['how_old'] + 1, 0 ) : '',
363 'before' => current_time( 'mysql' ),
364 'inclusive' => true,
365 ),
366 );
367 }
368
369 /**
370 * Filter the date_query passed to \WP_Query.
371 *
372 * @since 3.2.0
373 *
374 * @param array $date_query Array of date parameters to be passed to \WP_Query.
375 * @param array $args Arguments array.
376 */
377 $args['date_query'] = apply_filters( 'top_ten_query_date_query', $date_query, $args );
378
379 // Meta Query.
380 if ( ! empty( $args['meta_query'] ) && is_array( $args['meta_query'] ) ) {
381 $meta_query = $args['meta_query'];
382 } else {
383 $meta_query = array();
384 }
385
386 /**
387 * Filter the meta_query passed to \WP_Query.
388 *
389 * @since 3.2.0
390 *
391 * @param mixed $meta_query Array of meta_query parameters.
392 * @param array $args Arguments array.
393 */
394 $meta_query = apply_filters( 'top_ten_query_meta_query', $meta_query, $args ); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
395
396 // Validate meta_query structure.
397 if ( ! is_array( $meta_query ) ) {
398 $meta_query = array();
399 }
400
401 // Add a relation key if more than one $meta_query and if 'relation' is not already set.
402 if ( count( $meta_query ) > 1 && ! isset( $meta_query['relation'] ) ) {
403 /**
404 * Filter the meta_query relation parameter.
405 *
406 * @since 3.2.0
407 *
408 * @param string $relation The logical relationship between each inner meta_query array when there is more than one. Default is 'AND'.
409 * @param array $args Arguments array.
410 */
411 $meta_query['relation'] = apply_filters( 'top_ten_query_meta_query_relation', 'AND', $args );
412 }
413
414 $args['meta_query'] = $meta_query; // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
415
416 // Set post_status.
417 $args['post_status'] = empty( $args['post_status'] ) ? array( 'publish', 'inherit' ) : $args['post_status'];
418
419 // Unset what we don't need.
420 unset( $args['title'] );
421 unset( $args['title_daily'] );
422 unset( $args['blank_output'] );
423 unset( $args['blank_output_text'] );
424 unset( $args['show_excerpt'] );
425 unset( $args['excerpt_length'] );
426 unset( $args['show_date'] );
427 unset( $args['show_author'] );
428 unset( $args['disp_list_count'] );
429 unset( $args['title_length'] );
430 unset( $args['link_new_window'] );
431 unset( $args['link_nofollow'] );
432 unset( $args['before_list'] );
433 unset( $args['after_list'] );
434 unset( $args['before_list_item'] );
435 unset( $args['after_list_item'] );
436
437 /**
438 * Filters the arguments of the query.
439 *
440 * @since 3.0.0
441 *
442 * @param array $args The arguments of the query.
443 * @param Top_Ten_Core_Query $query The Top_Ten_Core_Query instance (passed by reference).
444 */
445 $this->query_args = apply_filters_ref_array( 'top_ten_query_args', array( $args, &$this ) );
446
447 if ( $this->should_cache() && ! $this->in_cache ) {
448 // Handle exclude_current_post by adding current_post_id for cache key consistency.
449 $cache_args = $this->query_args;
450 if ( ! empty( $cache_args['exclude_current_post'] ) ) {
451 $cache_args['current_post_id'] = (int) get_the_ID();
452 }
453 $this->cache_name = \WebberZone\Top_Ten\Util\Cache::get_key( $cache_args );
454 }
455 }
456
457 /**
458 * Modify the pre_get_posts clause.
459 *
460 * @since 4.0.0
461 *
462 * @param \WP_Query $query The WP_Query instance.
463 */
464 public function pre_get_posts( $query ) {
465
466 if ( true === $query->get( 'top_ten_query' ) ) {
467 $query_args_keys = array(
468 'date_query',
469 'tax_query',
470 'meta_query',
471 'post_type',
472 'post_status',
473 'posts_per_page',
474 'offset',
475 'author',
476 );
477
478 foreach ( $query_args_keys as $key ) {
479 if ( ! empty( $this->query_args[ $key ] ) ) {
480 $query->set( $key, $this->query_args[ $key ] );
481 }
482 }
483
484 $query->set( 'suppress_filters', false );
485 $query->set( 'no_found_rows', true );
486 $query->set( 'ignore_sticky_posts', true );
487
488 remove_filter( 'pre_get_posts', array( $this, 'pre_get_posts' ) );
489 }
490 }
491
492 /**
493 * Modify the SELECT clause - posts_fields.
494 *
495 * @since 3.0.0
496 *
497 * @param string $fields The SELECT clause of the query.
498 * @param \WP_Query $query The \WP_Query instance.
499 * @return string Updated Fields
500 */
501 public function posts_fields( $fields, $query ) {
502 // Return if it is not a Top_Ten_Core_Query.
503 if ( true !== $query->get( 'top_ten_query' ) ) {
504 return $fields;
505 }
506
507 $_fields[] = "{$this->table_name}.postnumber";
508 $_fields[] = $this->is_daily ? "SUM({$this->table_name}.cntaccess) as visits" : "{$this->table_name}.cntaccess as visits";
509 $_fields[] = "{$this->table_name}.blog_id";
510
511 $_fields = implode( ', ', $_fields );
512
513 $fields .= ',' . $_fields;
514
515 /**
516 * Filters the fields returned by the SELECT clause.
517 *
518 * @since 3.2.0
519 *
520 * @param string $fields The fields returned by the SELECT clause.
521 * @param Top_Ten_Core_Query $query The Top_Ten_Core_Query instance (passed by reference).
522 */
523 $fields = apply_filters_ref_array( 'top_ten_query_posts_fields', array( $fields, &$this ) );
524
525 remove_filter( 'posts_fields', array( $this, 'posts_fields' ) );
526
527 return $fields;
528 }
529
530 /**
531 * Modify the posts_join clause.
532 *
533 * @since 3.0.0
534 *
535 * @param string $join The JOIN clause of the query.
536 * @param \WP_Query $query The \WP_Query instance.
537 * @return string Updated JOIN
538 */
539 public function posts_join( $join, $query ) {
540 global $wpdb;
541
542 // Return if it is not a Top_Ten_Core_Query.
543 if ( true !== $query->get( 'top_ten_query' ) ) {
544 return $join;
545 }
546
547 $join .= " INNER JOIN {$this->table_name} ON {$this->table_name}.postnumber={$wpdb->posts}.ID ";
548
549 /**
550 * Filters the JOIN clause of Top_Ten_Core_Query.
551 *
552 * @since 3.2.0
553 *
554 * @param string $join The JOIN clause of the Query.
555 * @param Top_Ten_Core_Query $query The Top_Ten_Core_Query instance (passed by reference).
556 */
557 $join = apply_filters_ref_array( 'top_ten_query_posts_join', array( $join, &$this ) );
558
559 remove_filter( 'posts_join', array( $this, 'posts_join' ) );
560
561 return $join;
562 }
563
564 /**
565 * Modify the posts_where clause.
566 *
567 * @since 3.0.0
568 *
569 * @param string $where The WHERE clause of the query.
570 * @param \WP_Query $query The \WP_Query instance.
571 * @return string Updated WHERE
572 */
573 public function posts_where( $where, $query ) {
574 global $wpdb;
575
576 // Return if it is not a Top_Ten_Core_Query.
577 if ( true !== $query->get( 'top_ten_query' ) ) {
578 return $where;
579 }
580
581 if ( ! $this->multiple_blogs ) {
582 $where .= " AND {$this->table_name}.blog_id IN ('" . join( "', '", $this->blog_id ) . "') "; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
583 }
584
585 if ( $this->is_daily ) {
586 if ( isset( $this->query_args['from_date'] ) ) {
587 $from_date = Helpers::get_from_date( $this->query_args['from_date'], 0, 0 );
588 } else {
589 $from_date = Helpers::get_from_date( null, $this->query_args['daily_range'], $this->query_args['hour_range'] );
590 }
591 $where .= $wpdb->prepare( " AND {$this->table_name}.dp_date >= %s ", $from_date ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
592
593 if ( isset( $this->query_args['to_date'] ) ) {
594 $to_date = Helpers::get_from_date( $this->query_args['to_date'], 0, 0 );
595 $where .= $wpdb->prepare( " AND {$this->table_name}.dp_date <= %s ", $to_date ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
596 }
597 }
598
599 /**
600 * Filters the WHERE clause of Top_Ten_Core_Query.
601 *
602 * @since 3.2.0
603 *
604 * @param string $where The WHERE clause of the Query.
605 * @param Top_Ten_Core_Query $query The Top_Ten_Core_Query instance (passed by reference).
606 */
607 $where = apply_filters_ref_array( 'top_ten_query_posts_where', array( $where, &$this ) );
608
609 remove_filter( 'posts_where', array( $this, 'posts_where' ) );
610
611 return $where;
612 }
613
614 /**
615 * Modify the ORDER BY clause of the query.
616 *
617 * @since 3.0.0
618 *
619 * @param string $orderby The current ORDER BY clause.
620 * @param \WP_Query $query The current query instance.
621 * @return string Modified ORDER BY clause.
622 */
623 public function posts_orderby( $orderby, $query ) {
624 // Return if it is not a Top_Ten_Core_Query.
625 if ( true !== $query->get( 'top_ten_query' ) ) {
626 return $orderby;
627 }
628
629 // Use WP_Query's parse_order method to get a valid order.
630 $query_orderby = $query->get( 'orderby' );
631 $order = $query->parse_order( $query->get( 'order' ) );
632
633 // Preserve intentional orderby values except the plugin's visits alias.
634 if ( is_array( $query_orderby ) && isset( $query_orderby['visits'] ) ) {
635 $visits_order = $query->parse_order( $query_orderby['visits'] );
636 $orderby = empty( $orderby ) ? "visits $visits_order, {$this->table_name}.postnumber $visits_order" : "visits $visits_order, {$orderby}";
637 } elseif ( empty( $query_orderby ) || 'visits' === $query_orderby ) {
638 $orderby = "visits $order, {$this->table_name}.postnumber $order";
639 }
640
641 /**
642 * Filters the ORDER BY clause of Top_Ten_Core_Query.
643 *
644 * @since 3.2.0
645 *
646 * @param string $orderby The ORDER BY clause of the Query.
647 * @param Top_Ten_Core_Query $query The Top_Ten_Core_Query instance (passed by reference).
648 */
649 $orderby = apply_filters_ref_array( 'top_ten_query_posts_orderby', array( $orderby, &$this ) );
650
651 remove_filter( 'posts_orderby', array( $this, 'posts_orderby' ), 9999 );
652
653 return $orderby;
654 }
655
656 /**
657 * Modify the posts_groupby clause.
658 *
659 * @since 3.0.0
660 *
661 * @param string $groupby The GROUP BY clause of the query.
662 * @param \WP_Query $query The \WP_Query instance.
663 * @return string Updated GROUP BY
664 */
665 public function posts_groupby( $groupby, $query ) {
666
667 // Return if it is not a Top_Ten_Core_Query.
668 if ( true !== $query->get( 'top_ten_query' ) ) {
669 return $groupby;
670 }
671
672 if ( $this->is_daily ) {
673 $groupby = " {$this->table_name}.postnumber ";
674 }
675
676 /**
677 * Filters the GROUP BY clause of Top_Ten_Core_Query.
678 *
679 * @since 3.2.0
680 *
681 * @param string $groupby The GROUP BY clause of the Query.
682 * @param Top_Ten_Core_Query $query The Top_Ten_Core_Query instance (passed by reference).
683 */
684 $groupby = apply_filters_ref_array( 'top_ten_query_posts_groupby', array( $groupby, &$this ) );
685
686 remove_filter( 'posts_groupby', array( $this, 'posts_groupby' ) );
687
688 return $groupby;
689 }
690
691 /**
692 * Modify posts_clauses in WP_Query to handle multiple blogs.
693 *
694 * @since 3.2.0
695 *
696 * @param array $clauses Query clauses.
697 * @param \WP_Query $query The \WP_Query instance.
698 * @return array Updated Query Clauses.
699 */
700 public function posts_clauses( $clauses, $query ) {
701
702 // Return if it is not a Top_Ten_Core_Query.
703 if ( true !== $query->get( 'top_ten_query' ) ) {
704 return $clauses;
705 }
706
707 $this->ms_select = array();
708
709 if ( $this->multiple_blogs ) {
710 global $wpdb;
711
712 $root_site_db_prefix = $wpdb->prefix;
713
714 foreach ( $this->blog_id as $blog_id ) {
715 switch_to_blog( $blog_id );
716
717 $ms_select = "
718 SELECT {$clauses['fields']}
719 FROM {$root_site_db_prefix}posts {$clauses['join']}
720 WHERE 1=1 {$clauses['where']}
721 ";
722 $ms_select .= $wpdb->prepare( " AND {$this->table_name}.blog_id = %d", $blog_id ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
723
724 if ( $this->is_daily ) {
725 $ms_select .= " GROUP BY {$this->table_name}.postnumber ";
726 }
727
728 $ms_select = str_replace( $root_site_db_prefix, $wpdb->prefix, $ms_select );
729 $ms_select = str_replace( "{$wpdb->prefix}top_ten", "{$wpdb->base_prefix}top_ten", $ms_select );
730
731 $this->ms_select[] = $ms_select;
732
733 restore_current_blog();
734 }
735
736 // Update the clauses.
737 $clauses['fields'] = 'tables.*';
738 $clauses['where'] = '';
739 $clauses['join'] = '';
740 $clauses['groupby'] = '';
741 }
742
743 /**
744 * Filters all query clauses at once, for convenience.
745 *
746 * @since 3.2.0
747 *
748 * @param array $clauses The GROUP BY clause of the Query.
749 * @param Top_Ten_Core_Query $query The Top_Ten_Core_Query instance (passed by reference).
750 */
751 $clauses = apply_filters_ref_array( 'top_ten_query_posts_clauses', array( $clauses, &$this ) );
752
753 return $clauses;
754 }
755
756 /**
757 * Modify the completed SQL query before sending.
758 *
759 * @since 3.2.0
760 *
761 * @param string $sql The complete SQL query.
762 * @param \WP_Query $query The \WP_Query instance (passed by reference).
763 * @return string Updated SQL.
764 */
765 public function posts_request( $sql, $query ) {
766
767 // Return if it is not a Top_Ten_Core_Query.
768 if ( true !== $query->get( 'top_ten_query' ) ) {
769 return $sql;
770 }
771
772 if ( $this->multiple_blogs ) {
773 global $wpdb;
774
775 // Clean up remanescent WHERE request.
776 $sql = str_replace( 'WHERE 1=1', '', $sql );
777
778 // Multisite request.
779 $sql = str_replace( "FROM $wpdb->posts", 'FROM ( ' . implode( ' UNION ', $this->ms_select ) . ' ) tables', $sql );
780
781 }
782
783 /**
784 * Filters the completed SQL query before sending.
785 *
786 * @since 3.2.0
787 *
788 * @param string $sql The complete SQL query.
789 * @param Top_Ten_Core_Query $query The \WP_Query instance (passed by reference).
790 */
791 $sql = apply_filters_ref_array( 'top_ten_query_posts_request', array( $sql, &$this ) );
792
793 remove_filter( 'posts_request', array( $this, 'posts_request' ) );
794
795 return $sql;
796 }
797
798 /**
799 * Update the_post while \WP_Query loops through this.
800 *
801 * @since 3.2.0
802 *
803 * @param \WP_Post $post The Post object (passed by reference).
804 */
805 public function switch_to_blog_in_loop( $post ) {
806 global $blog_id;
807 if ( $this->multiple_blogs ) {
808 if ( isset( $post->blog_id ) && (int) $blog_id !== (int) $post->blog_id ) {
809 switch_to_blog( $post->blog_id );
810 } else {
811 restore_current_blog();
812 }
813 }
814 }
815
816 /**
817 * Restore current blog on loop end.
818 *
819 * @since 3.2.0
820 */
821 public function loop_end() {
822 if ( $this->multiple_blogs ) {
823 restore_current_blog();
824 }
825 }
826
827 /**
828 * Filter posts_pre_query to allow caching to work.
829 *
830 * @since 3.0.0
831 *
832 * @param \WP_Post[] $posts Array of post objects.
833 * @param \WP_Query $query The WP_Query instance (passed by reference).
834 * @return \WP_Post[] Updated Array of post objects.
835 */
836 public function posts_pre_query( $posts, $query ) {
837
838 // Return if it is not a Top_Ten_Core_Query.
839 if ( true !== $query->get( 'top_ten_query' ) ) {
840 return $posts;
841 }
842
843 // Check the cache if there are any posts saved.
844 if ( $this->should_cache() ) {
845
846 $cache_name = $this->cache_name;
847
848 $post_ids = get_transient( $cache_name );
849
850 if ( ! empty( $post_ids ) ) {
851 $posts = get_posts(
852 array(
853 'post__in' => array_unique( $post_ids ),
854 'fields' => $query->get( 'fields' ),
855 'orderby' => 'post__in',
856 'post_type' => $query->get( 'post_type' ),
857 'post_status' => $query->get( 'post_status' ),
858 'numberposts' => $query->get( 'posts_per_page' ),
859 )
860 );
861 $query->found_posts = count( $posts );
862 $query->max_num_pages = (int) ceil( $query->found_posts / $query->get( 'posts_per_page' ) );
863 $this->in_cache = true;
864 }
865 }
866
867 /**
868 * Filters the posts_pre_query of Top_Ten_Query after processing and before returning.
869 *
870 * @since 4.0.0
871 *
872 * @param \WP_Post[] $posts Array of post data.
873 * @param \WP_Query $query The WP_Query instance.
874 */
875 $posts = apply_filters( 'top_ten_query_posts_pre_query', $posts, $query );
876
877 remove_filter( 'posts_pre_query', array( $this, 'posts_pre_query' ) );
878
879 return $posts;
880 }
881
882 /**
883 * Modify the array of retrieved posts.
884 *
885 * @since 3.0.0
886 *
887 * @param \WP_Post[] $posts Array of post objects.
888 * @param \WP_Query $query The \WP_Query instance (passed by reference).
889 * @return \WP_Post[] Updated Array of post objects.
890 */
891 public function the_posts( $posts, $query ) {
892
893 // Return if it is not a Top_Ten_Core_Query.
894 if ( true !== $query->get( 'top_ten_query' ) ) {
895 return $posts;
896 }
897
898 // Support caching to speed up retrieval.
899 if ( $this->should_cache() && ! $this->in_cache ) {
900 /** This filter is defined in display-posts.php */
901 $cache_time = apply_filters( 'tptn_cache_time', $this->query_args['cache_time'], $this->query_args );
902 $cache_name = $this->cache_name;
903 $post_ids = wp_list_pluck( $query->posts, 'ID' );
904
905 set_transient( $cache_name, $post_ids, $cache_time );
906 }
907
908 if ( ! empty( $this->query_args['include_post_ids'] ) ) { // phpcs:ignore WordPress.PHP.StrictComparisons.LooseComparison
909 $include_post_ids = wp_parse_id_list( $this->query_args['include_post_ids'] );
910 }
911 if ( ! empty( $include_post_ids ) ) {
912 $extra_posts = get_posts(
913 array(
914 'include' => $include_post_ids,
915 'fields' => $query->get( 'fields' ),
916 'orderby' => 'post__in',
917 'post_type' => $query->get( 'post_type' ),
918 'post_status' => $query->get( 'post_status' ),
919 )
920 );
921 $posts = array_merge( $extra_posts, $posts );
922 }
923
924 // Shuffle posts if random order is set.
925 if ( $this->random_order ) {
926 shuffle( $posts );
927 }
928
929 /**
930 * Filter array of WP_Post objects before it is returned to the Top_Ten_Core_Query instance.
931 *
932 * @since 3.0.0
933 *
934 * @param \WP_Post[] $posts Array of post objects.
935 * @param array $args Arguments array.
936 */
937 $posts = apply_filters( 'top_ten_query_the_posts', $posts, $this->query_args );
938
939 remove_filter( 'the_posts', array( $this, 'the_posts' ) );
940
941 return $posts;
942 }
943
944 /**
945 * Determines if caching should be enabled for the current query.
946 *
947 * @since 4.1.0
948 *
949 * @return bool Whether caching should be enabled.
950 */
951 protected function should_cache() {
952 return ! empty( $this->query_args['cache_posts'] ) && ! ( is_preview() || is_admin() || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) );
953 }
954 }
955