PluginProbe
GamiPress – Gamification plugin to reward points, badges & ranks in WordPress, now with AI / 8.0.5
GamiPress – Gamification plugin to reward points, badges & ranks in WordPress, now with AI v8.0.5
8.0.5 8.0.4 8.0.3 8.0.1 8.0.2 8.0.0 7.9.9.7 7.9.9.6 7.9.9.5 7.9.9.4 7.9.9.3 7.9.9.2 7.9.9.1 7.9.9 7.9.8 7.9.7 7.9.6 7.9.5 7.9.4 7.9.3 7.9.2 7.9.1 7.9.0 7.8.9 7.8.8 All 47 releases
← All changes | libraries/ct/includes/class-ct-query.php +1105 -0 7.8.9 → 8.0.5 View file →
@@ -1,0 +1,1105 @@
1 +<?php
2 +/**
3 + * Query class
4 + *
5 + * Based on WP_Query class
6 + *
7 + * @author GamiPress <[email protected]>, Ruben Garcia <[email protected]>
8 + *
9 + * @since 1.0.0
10 + */
11 +// Exit if accessed directly
12 +defined( 'ABSPATH' ) || exit;
13 +
14 +if ( ! class_exists( 'CT_Query' ) ) :
15 +
16 + class CT_Query {
17 +
18 + /**
19 + * Query vars set by the user
20 + *
21 + * @since 1.0.0
22 + * @access public
23 + * @var array
24 + */
25 + public $query;
26 +
27 + /**
28 + * Query vars, after parsing
29 + *
30 + * @since 1.0.0
31 + * @access public
32 + * @var array
33 + */
34 + public $query_vars = array();
35 +
36 + /**
37 + * Holds the data for a single object that is queried.
38 + *
39 + * Holds the contents of a post, page, category, attachment.
40 + *
41 + * @since 1.0.0
42 + * @access public
43 + * @var object|array
44 + */
45 + public $queried_object;
46 +
47 + /**
48 + * The ID of the queried object.
49 + *
50 + * @since 1.0.0
51 + * @access public
52 + * @var int
53 + */
54 + public $queried_object_id;
55 +
56 + /**
57 + * Get post database query.
58 + *
59 + * @since 1.0.0
60 + * @access public
61 + * @var string
62 + */
63 + public $request;
64 +
65 + /**
66 + * List of posts.
67 + *
68 + * @since 1.0.0
69 + * @access public
70 + * @var array
71 + */
72 + public $results;
73 +
74 + /**
75 + * The amount of posts for the current query.
76 + *
77 + * @since 1.0.0
78 + * @access public
79 + * @var int
80 + */
81 + public $result_count = 0;
82 +
83 + /**
84 + * Index of the current item in the loop.
85 + *
86 + * @since 1.0.0
87 + * @access public
88 + * @var int
89 + */
90 + public $current_result = -1;
91 +
92 + /**
93 + * Whether the loop has started and the caller is in the loop.
94 + *
95 + * @since 1.0.0
96 + * @access public
97 + * @var bool
98 + */
99 + public $in_the_loop = false;
100 +
101 + /**
102 + * The current post.
103 + *
104 + * @since 1.0.0
105 + * @access public
106 + * @var WP_Post
107 + */
108 + public $result;
109 +
110 + /**
111 + * The amount of found posts for the current query.
112 + *
113 + * If limit clause was not used, equals $post_count.
114 + *
115 + * @since 1.0.0
116 + * @access public
117 + * @var int
118 + */
119 + public $found_results = 0;
120 +
121 + /**
122 + * The amount of pages.
123 + *
124 + * @since 1.0.0
125 + * @access public
126 + * @var int
127 + */
128 + public $max_num_pages = 0;
129 +
130 + /**
131 + * stopwords used when parsing search terms
132 + *
133 + * @since 1.0.0
134 + * @access public
135 + * @var array
136 + */
137 + public $stopwords = array();
138 +
139 +
140 + public function __construct( $query = '' ) {
141 +
142 + $this->init();
143 +
144 + $this->query = $this->query_vars = wp_parse_args( $query );
145 +
146 + }
147 +
148 + public function init() {
149 +
150 + unset($this->results);
151 + unset($this->query);
152 + $this->query_vars = array();
153 + unset($this->queried_object);
154 + unset($this->queried_object_id);
155 + $this->result_count = 0;
156 + $this->current_result = -1;
157 + $this->in_the_loop = false;
158 + unset( $this->request );
159 + unset( $this->result );
160 + $this->found_results = 0;
161 + $this->max_num_pages = 0;
162 + $this->stopwords = array();
163 +
164 + }
165 +
166 + public function set( $query_var, $value ) {
167 + $this->query_vars[$query_var] = $value;
168 + }
169 +
170 + public function get_results() {
171 +
172 + global $wpdb, $ct_table;
173 +
174 + $this->parse_query();
175 +
176 + /**
177 + * Fires after the query variable object is created, but before the actual query is run.
178 + *
179 + * Note: If using conditional tags, use the method versions within the passed instance
180 + * (e.g. $this->is_main_query() instead of is_main_query()). This is because the functions
181 + * like is_main_query() test against the global $wp_query instance, not the passed one.
182 + *
183 + * @since 1.0.0
184 + *
185 + * @param CT_Query &$this The CT_Query instance (passed by reference).
186 + */
187 + do_action_ref_array( 'ct_pre_get_results', array( &$this ) );
188 +
189 + // Shorthand.
190 + $q = &$this->query_vars;
191 +
192 + // Fill again in case pre_get_posts unset some vars.
193 + $q = $this->fill_query_vars($q);
194 +
195 + // Suppress filters
196 + if ( ! isset($q['suppress_filters']) )
197 + $q['suppress_filters'] = false;
198 +
199 + if ( empty( $q['items_per_page'] ) ) {
200 + $q['items_per_page'] = get_option( 'items_per_page', 20 );
201 + }
202 +
203 + // Pagination
204 + if ( ! isset( $q['nopaging'] ) ) {
205 + if ( $q['items_per_page'] == -1 ) {
206 + $q['nopaging'] = true;
207 + } else {
208 + $q['nopaging'] = false;
209 + }
210 + }
211 +
212 + // Items per page
213 + $q['items_per_page'] = (int) $q['items_per_page'];
214 +
215 + if ( $q['items_per_page'] < -1 )
216 + $q['items_per_page'] = abs($q['items_per_page']);
217 + elseif ( $q['items_per_page'] == 0 )
218 + $q['items_per_page'] = 1;
219 +
220 + // page
221 + if ( isset($q['page']) ) {
222 + $q['page'] = trim($q['page'], '/');
223 + $q['page'] = absint($q['page']);
224 + }
225 +
226 + // If true, forcibly turns off SQL_CALC_FOUND_ROWS even when limits are present.
227 + if ( isset($q['no_found_rows']) ) {
228 + $q['no_found_rows'] = (bool) $q['no_found_rows'];
229 + } else {
230 + $q['no_found_rows'] = false;
231 + }
232 +
233 + switch ( $q['fields'] ) {
234 + case 'ids':
235 + $fields = "{$ct_table->db->table_name}.{$ct_table->db->primary_key}";
236 + break;
237 + default:
238 + $fields = "{$ct_table->db->table_name}.*";
239 + }
240 +
241 + // First let's clear some variables
242 + $distinct = '';
243 + $where = '';
244 + $limits = '';
245 + $join = '';
246 + $search = '';
247 + $groupby = '';
248 + $orderby = '';
249 + $page = 1;
250 +
251 + // If a search pattern is specified, load the posts that match.
252 + if ( strlen( $q['s'] ) ) {
253 + $search = $this->parse_search( $q );
254 + }
255 +
256 + if ( ! $q['suppress_filters'] ) {
257 + /**
258 + * Filters the search SQL that is used in the WHERE clause of WP_Query.
259 + *
260 + * @since 1.0.0
261 + *
262 + * @param string $search Search SQL for WHERE clause.
263 + * @param WP_Query $this The current WP_Query object.
264 + */
265 + $search = apply_filters_ref_array( 'ct_query_search', array( $search, &$this ) );
266 + }
267 +
268 + $where .= $search;
269 +
270 + // Rand order by
271 + $rand = ( isset( $q['orderby'] ) && 'rand' === $q['orderby'] );
272 + if ( ! isset( $q['order'] ) ) {
273 + $q['order'] = $rand ? '' : 'DESC';
274 + } else {
275 + $q['order'] = $rand ? '' : $this->parse_order( $q['order'] );
276 + }
277 +
278 + // Order by.
279 + if ( empty( $q['orderby'] ) ) {
280 + /*
281 + * Boolean false or empty array blanks out ORDER BY,
282 + * while leaving the value unset or otherwise empty sets the default.
283 + */
284 + if ( isset( $q['orderby'] ) && ( is_array( $q['orderby'] ) || false === $q['orderby'] ) ) {
285 + $orderby = '';
286 + } else {
287 + // Default order by primary key
288 + $orderby = "{$ct_table->db->table_name}.{$ct_table->db->primary_key} " . $q['order'];
289 + }
290 + } elseif ( 'none' == $q['orderby'] ) {
291 + $orderby = '';
292 + } else {
293 + $orderby_array = array();
294 + if ( is_array( $q['orderby'] ) ) {
295 + foreach ( $q['orderby'] as $_orderby => $order ) {
296 + $orderby = wp_slash( urldecode( $_orderby ) );
297 + $parsed = $this->parse_orderby( $orderby );
298 +
299 + // Only allow certain values for safety.
300 + if ( ! $parsed ) {
301 + continue;
302 + }
303 +
304 + $orderby_array[] = $parsed . ' ' . $this->parse_order( $order );
305 + }
306 + $orderby = implode( ', ', $orderby_array );
307 +
308 + } else {
309 + $q['orderby'] = wp_slash( urldecode( $q['orderby'] ) );
310 +
311 + foreach ( explode( ' ', $q['orderby'] ) as $i => $orderby ) {
312 +
313 + $parsed = $this->parse_orderby( $orderby );
314 +
315 + // Only allow certain values for safety.
316 + if ( ! $parsed ) {
317 + continue;
318 + }
319 +
320 + $orderby_array[] = $parsed;
321 + }
322 +
323 + $orderby = implode( ' ' . $q['order'] . ', ', $orderby_array );
324 +
325 + if ( empty( $orderby ) ) {
326 + $orderby = "{$ct_table->db->table_name}.{$ct_table->db->primary_key} " . $q['order'];
327 + } elseif ( ! empty( $q['order'] ) ) {
328 + $orderby .= " {$q['order']}";
329 + }
330 + }
331 + }
332 +
333 + /*
334 + * Apply filters on where and join prior to paging so that any
335 + * manipulations to them are reflected in the paging by day queries.
336 + */
337 + if ( !$q['suppress_filters'] ) {
338 + /**
339 + * Filters the WHERE clause of the query.
340 + *
341 + * @since 1.0.0
342 + *
343 + * @param string $where The WHERE clause of the query.
344 + * @param WP_Query &$this The WP_Query instance (passed by reference).
345 + */
346 + $where = apply_filters_ref_array( 'ct_query_where', array( $where, &$this ) );
347 +
348 + /**
349 + * Filters the JOIN clause of the query.
350 + *
351 + * @since 1.0.0
352 + *
353 + * @param string $where The JOIN clause of the query.
354 + * @param WP_Query &$this The WP_Query instance (passed by reference).
355 + */
356 + $join = apply_filters_ref_array( 'ct_query_join', array( $join, &$this ) );
357 + }
358 +
359 + // Paging
360 + if ( empty($q['nopaging']) ) {
361 + $page = absint($q['paged']);
362 + if ( !$page )
363 + $page = 1;
364 +
365 + // If 'offset' is provided, it takes precedence over 'paged'.
366 + if ( isset( $q['offset'] ) && is_numeric( $q['offset'] ) ) {
367 + $q['offset'] = absint( $q['offset'] );
368 + $pgstrt = $q['offset'] . ', ';
369 + } else {
370 + $pgstrt = absint( ( $page - 1 ) * $q['items_per_page'] ) . ', ';
371 + }
372 + $limits = 'LIMIT ' . $pgstrt . $q['items_per_page'];
373 + }
374 +
375 + $pieces = array( 'where', 'groupby', 'join', 'orderby', 'distinct', 'fields', 'limits' );
376 +
377 + /*
378 + * Apply post-paging filters on where and join. Only plugins that
379 + * manipulate paging queries should use these hooks.
380 + */
381 + if ( !$q['suppress_filters'] ) {
382 + /**
383 + * Filters the WHERE clause of the query.
384 + *
385 + * Specifically for manipulating paging queries.
386 + *
387 + * @since 1.0.0
388 + *
389 + * @param string $where The WHERE clause of the query.
390 + * @param WP_Query &$this The WP_Query instance (passed by reference).
391 + */
392 + $where = apply_filters_ref_array( 'ct_query_where_paged', array( $where, &$this ) );
393 +
394 + /**
395 + * Filters the GROUP BY clause of the query.
396 + *
397 + * @since 1.0.0
398 + *
399 + * @param string $groupby The GROUP BY clause of the query.
400 + * @param WP_Query &$this The WP_Query instance (passed by reference).
401 + */
402 + $groupby = apply_filters_ref_array( 'ct_query_groupby', array( $groupby, &$this ) );
403 +
404 + /**
405 + * Filters the JOIN clause of the query.
406 + *
407 + * Specifically for manipulating paging queries.
408 + *
409 + * @since 1.0.0
410 + *
411 + * @param string $join The JOIN clause of the query.
412 + * @param WP_Query &$this The WP_Query instance (passed by reference).
413 + */
414 + $join = apply_filters_ref_array( 'ct_query_join_paged', array( $join, &$this ) );
415 +
416 + /**
417 + * Filters the ORDER BY clause of the query.
418 + *
419 + * @since 1.0.0
420 + *
421 + * @param string $orderby The ORDER BY clause of the query.
422 + * @param WP_Query &$this The WP_Query instance (passed by reference).
423 + */
424 + $orderby = apply_filters_ref_array( 'ct_query_orderby', array( $orderby, &$this ) );
425 +
426 + /**
427 + * Filters the DISTINCT clause of the query.
428 + *
429 + * @since 1.0.0
430 + *
431 + * @param string $distinct The DISTINCT clause of the query.
432 + * @param WP_Query &$this The WP_Query instance (passed by reference).
433 + */
434 + $distinct = apply_filters_ref_array( 'ct_query_distinct', array( $distinct, &$this ) );
435 +
436 + /**
437 + * Filters the LIMIT clause of the query.
438 + *
439 + * @since 1.0.0
440 + *
441 + * @param string $limits The LIMIT clause of the query.
442 + * @param WP_Query &$this The WP_Query instance (passed by reference).
443 + */
444 + $limits = apply_filters_ref_array( 'ct_querylimits', array( $limits, &$this ) );
445 +
446 + /**
447 + * Filters the SELECT clause of the query.
448 + *
449 + * @since 1.0.0
450 + *
451 + * @param string $fields The SELECT clause of the query.
452 + * @param WP_Query &$this The WP_Query instance (passed by reference).
453 + */
454 + $fields = apply_filters_ref_array( 'ct_query_fields', array( $fields, &$this ) );
455 +
456 + /**
457 + * Filters all query clauses at once, for convenience.
458 + *
459 + * Covers the WHERE, GROUP BY, JOIN, ORDER BY, DISTINCT,
460 + * fields (SELECT), and LIMITS clauses.
461 + *
462 + * @since 1.0.0
463 + *
464 + * @param array $clauses The list of clauses for the query.
465 + * @param WP_Query &$this The WP_Query instance (passed by reference).
466 + */
467 + $clauses = (array) apply_filters_ref_array( 'ct_query_clauses', array( compact( $pieces ), &$this ) );
468 +
469 + $where = isset( $clauses[ 'where' ] ) ? $clauses[ 'where' ] : '';
470 + $groupby = isset( $clauses[ 'groupby' ] ) ? $clauses[ 'groupby' ] : '';
471 + $join = isset( $clauses[ 'join' ] ) ? $clauses[ 'join' ] : '';
472 + $orderby = isset( $clauses[ 'orderby' ] ) ? $clauses[ 'orderby' ] : '';
473 + $distinct = isset( $clauses[ 'distinct' ] ) ? $clauses[ 'distinct' ] : '';
474 + $fields = isset( $clauses[ 'fields' ] ) ? $clauses[ 'fields' ] : '';
475 + $limits = isset( $clauses[ 'limits' ] ) ? $clauses[ 'limits' ] : '';
476 + }
477 +
478 + /**
479 + * Fires to announce the query's current selection parameters.
480 + *
481 + * For use by caching plugins.
482 + *
483 + * @since 1.0.0
484 + *
485 + * @param string $selection The assembled selection query.
486 + */
487 + do_action( 'ct_query_selection', $where . $groupby . $orderby . $limits . $join );
488 +
489 + /*
490 + * Filters again for the benefit of caching plugins.
491 + * Regular plugins should use the hooks above.
492 + */
493 + if ( !$q['suppress_filters'] ) {
494 + /**
495 + * Filters the WHERE clause of the query.
496 + *
497 + * For use by caching plugins.
498 + *
499 + * @since 1.0.0
500 + *
501 + * @param string $where The WHERE clause of the query.
502 + * @param WP_Query &$this The WP_Query instance (passed by reference).
503 + */
504 + $where = apply_filters_ref_array( 'ct_query_where_request', array( $where, &$this ) );
505 +
506 + /**
507 + * Filters the GROUP BY clause of the query.
508 + *
509 + * For use by caching plugins.
510 + *
511 + * @since 1.0.0
512 + *
513 + * @param string $groupby The GROUP BY clause of the query.
514 + * @param WP_Query &$this The WP_Query instance (passed by reference).
515 + */
516 + $groupby = apply_filters_ref_array( 'ct_query_groupby_request', array( $groupby, &$this ) );
517 +
518 + /**
519 + * Filters the JOIN clause of the query.
520 + *
521 + * For use by caching plugins.
522 + *
523 + * @since 1.0.0
524 + *
525 + * @param string $join The JOIN clause of the query.
526 + * @param WP_Query &$this The WP_Query instance (passed by reference).
527 + */
528 + $join = apply_filters_ref_array( 'ct_query_join_request', array( $join, &$this ) );
529 +
530 + /**
531 + * Filters the ORDER BY clause of the query.
532 + *
533 + * For use by caching plugins.
534 + *
535 + * @since 1.0.0
536 + *
537 + * @param string $orderby The ORDER BY clause of the query.
538 + * @param WP_Query &$this The WP_Query instance (passed by reference).
539 + */
540 + $orderby = apply_filters_ref_array( 'ct_query_orderby_request', array( $orderby, &$this ) );
541 +
542 + /**
543 + * Filters the DISTINCT clause of the query.
544 + *
545 + * For use by caching plugins.
546 + *
547 + * @since 1.0.0
548 + *
549 + * @param string $distinct The DISTINCT clause of the query.
550 + * @param WP_Query &$this The WP_Query instance (passed by reference).
551 + */
552 + $distinct = apply_filters_ref_array( 'ct_query_distinct_request', array( $distinct, &$this ) );
553 +
554 + /**
555 + * Filters the SELECT clause of the query.
556 + *
557 + * For use by caching plugins.
558 + *
559 + * @since 1.0.0
560 + *
561 + * @param string $fields The SELECT clause of the query.
562 + * @param WP_Query &$this The WP_Query instance (passed by reference).
563 + */
564 + $fields = apply_filters_ref_array( 'ct_query_fields_request', array( $fields, &$this ) );
565 +
566 + /**
567 + * Filters the LIMIT clause of the query.
568 + *
569 + * For use by caching plugins.
570 + *
571 + * @since 1.0.0
572 + *
573 + * @param string $limits The LIMIT clause of the query.
574 + * @param WP_Query &$this The WP_Query instance (passed by reference).
575 + */
576 + $limits = apply_filters_ref_array( 'ct_query_limits_request', array( $limits, &$this ) );
577 +
578 + /**
579 + * Filters all query clauses at once, for convenience.
580 + *
581 + * For use by caching plugins.
582 + *
583 + * Covers the WHERE, GROUP BY, JOIN, ORDER BY, DISTINCT,
584 + * fields (SELECT), and LIMITS clauses.
585 + *
586 + * @since 1.0.0
587 + *
588 + * @param array $pieces The pieces of the query.
589 + * @param WP_Query &$this The WP_Query instance (passed by reference).
590 + */
591 + $clauses = (array) apply_filters_ref_array( 'ct_query_clauses_request', array( compact( $pieces ), &$this ) );
592 +
593 + $where = isset( $clauses[ 'where' ] ) ? $clauses[ 'where' ] : '';
594 + $groupby = isset( $clauses[ 'groupby' ] ) ? $clauses[ 'groupby' ] : '';
595 + $join = isset( $clauses[ 'join' ] ) ? $clauses[ 'join' ] : '';
596 + $orderby = isset( $clauses[ 'orderby' ] ) ? $clauses[ 'orderby' ] : '';
597 + $distinct = isset( $clauses[ 'distinct' ] ) ? $clauses[ 'distinct' ] : '';
598 + $fields = isset( $clauses[ 'fields' ] ) ? $clauses[ 'fields' ] : '';
599 + $limits = isset( $clauses[ 'limits' ] ) ? $clauses[ 'limits' ] : '';
600 + }
601 +
602 + if ( ! empty($groupby) )
603 + $groupby = 'GROUP BY ' . $groupby;
604 + if ( !empty( $orderby ) )
605 + $orderby = 'ORDER BY ' . $orderby;
606 +
607 + $found_rows = '';
608 + if ( !$q['no_found_rows'] && !empty($limits) )
609 + $found_rows = 'SQL_CALC_FOUND_ROWS';
610 +
611 + $this->request = $old_request = "SELECT $found_rows $distinct $fields FROM {$ct_table->db->table_name} $join WHERE 1=1 $where $groupby $orderby $limits";
612 +
613 + if ( !$q['suppress_filters'] ) {
614 + /**
615 + * Filters the completed SQL query before sending.
616 + *
617 + * @since 1.0.0
618 + *
619 + * @param string $request The complete SQL query.
620 + * @param WP_Query &$this The WP_Query instance (passed by reference).
621 + */
622 + $this->request = apply_filters_ref_array( 'ct_query_request', array( $this->request, &$this ) );
623 + }
624 +
625 + /**
626 + * Filters the posts array before the query takes place.
627 + *
628 + * Return a non-null value to bypass WordPress's default post queries.
629 + *
630 + * Filtering functions that require pagination information are encouraged to set
631 + * the `found_posts` and `max_num_pages` properties of the WP_Query object,
632 + * passed to the filter by reference. If WP_Query does not perform a database
633 + * query, it will not have enough information to generate these values itself.
634 + *
635 + * @since 1.0.0
636 + *
637 + * @param array|null $posts Return an array of post data to short-circuit WP's query,
638 + * or null to allow WP to run its normal queries.
639 + * @param WP_Query $this The WP_Query instance, passed by reference.
640 + */
641 + $this->results = apply_filters_ref_array( 'ct_results_pre_query', array( null, &$this ) );
642 +
643 + if ( 'ids' == $q['fields'] ) {
644 + if ( null === $this->results ) {
645 + $this->results = $wpdb->get_col( $this->request );
646 + }
647 +
648 + $this->results = array_map( 'intval', $this->results );
649 + $this->result_count = count( $this->results );
650 + $this->set_found_results( $q, $limits );
651 +
652 + return $this->results;
653 + }
654 +
655 + if ( null === $this->results ) {
656 + $split_the_query = ( $old_request == $this->request && "{$ct_table->db->table_name}.*" == $fields && !empty( $limits ) && $q['items_per_page'] < 500 );
657 +
658 + /**
659 + * Filters whether to split the query.
660 + *
661 + * Splitting the query will cause it to fetch just the IDs of the found posts
662 + * (and then individually fetch each post by ID), rather than fetching every
663 + * complete row at once. One massive result vs. many small results.
664 + *
665 + * @since 1.0.0
666 + *
667 + * @param bool $split_the_query Whether or not to split the query.
668 + * @param WP_Query $this The WP_Query instance.
669 + */
670 + $split_the_query = apply_filters( 'split_the_query', $split_the_query, $this );
671 +
672 + if ( $split_the_query ) {
673 + // First get the IDs and then fill in the objects
674 +
675 + $this->request = "SELECT $found_rows $distinct {$ct_table->db->table_name}.{$ct_table->db->primary_key} FROM {$ct_table->db->table_name} $join WHERE 1=1 $where $groupby $orderby $limits";
676 +
677 + /**
678 + * Filters the Post IDs SQL request before sending.
679 + *
680 + * @since 1.0.0
681 + *
682 + * @param string $request The post ID request.
683 + * @param WP_Query $this The WP_Query instance.
684 + */
685 + $this->request = apply_filters( 'ct_query_request_ids', $this->request, $this );
686 +
687 + $ids = $wpdb->get_col( $this->request );
688 +
689 + if ( $ids ) {
690 + $this->results = $ids;
691 + $this->set_found_results( $q, $limits );
692 + // TODO: Add caching utility
693 + //_prime_post_caches( $ids, $q['update_post_term_cache'], $q['update_post_meta_cache'] );
694 + } else {
695 + $this->results = array();
696 + }
697 + } else {
698 + $this->results = $wpdb->get_results( $this->request );
699 + $this->set_found_results( $q, $limits );
700 + }
701 + }
702 +
703 + // Convert to objects.
704 + if ( $this->results ) {
705 + $this->results = array_map( 'ct_get_object', $this->results );
706 + }
707 +
708 + if ( ! $q['suppress_filters'] ) {
709 + /**
710 + * Filters the raw post results array, prior to status checks.
711 + *
712 + * @since 1.0.0
713 + *
714 + * @param array $posts The post results array.
715 + * @param WP_Query &$this The WP_Query instance (passed by reference).
716 + */
717 + $this->results = apply_filters_ref_array( 'ct_query_results', array( $this->results, &$this ) );
718 + }
719 +
720 + if ( ! $q['suppress_filters'] ) {
721 + /**
722 + * Filters the array of retrieved posts after they've been fetched and
723 + * internally processed.
724 + *
725 + * @since 1.0.0
726 + *
727 + * @param array $posts The array of retrieved posts.
728 + * @param WP_Query &$this The WP_Query instance (passed by reference).
729 + */
730 + $this->results = apply_filters_ref_array( 'ct_the_results', array( $this->results, &$this ) );
731 + }
732 +
733 + // Ensure that any posts added/modified via one of the filters above are
734 + // of the type WP_Post and are filtered.
735 + if ( $this->results ) {
736 + $this->result_count = count( $this->results );
737 +
738 + $this->results = array_map( 'ct_get_object', $this->results );
739 +
740 + //if ( $q['cache_results'] )
741 + //update_post_caches($this->posts, $post_type, $q['update_post_term_cache'], $q['update_post_meta_cache']);
742 +
743 + //$this->post = reset( $this->posts );
744 + } else {
745 + $this->result_count = 0;
746 + $this->results = array();
747 + }
748 +
749 + return $this->results;
750 +
751 + }
752 +
753 + public function parse_query( $query = '' ) {
754 +
755 + if ( ! empty( $query ) ) {
756 + $this->init();
757 + $this->query = $this->query_vars = wp_parse_args( $query );
758 + } elseif ( ! isset( $this->query ) ) {
759 + $this->query = $this->query_vars;
760 + }
761 +
762 + $this->query_vars = $this->fill_query_vars($this->query_vars);
763 +
764 + $qv = &$this->query_vars;
765 +
766 + $qv['paged'] = absint($qv['paged']);
767 +
768 + // Fairly insane upper bound for search string lengths.
769 + if ( ! is_scalar( $qv['s'] ) || ( ! empty( $qv['s'] ) && strlen( $qv['s'] ) > 1600 ) ) {
770 + $qv['s'] = '';
771 + }
772 +
773 + /**
774 + * Fires after the main query vars have been parsed.
775 + *
776 + * @since 1.0.0
777 + *
778 + * @param WP_Query &$this The WP_Query instance (passed by reference).
779 + */
780 + do_action_ref_array( 'ct_parse_query', array( &$this ) );
781 +
782 + }
783 +
784 + /**
785 + * Fills in the query variables, which do not exist within the parameter.
786 + *
787 + * @since 1.0.0
788 + * @access public
789 + *
790 + * @param array $array Defined query variables.
791 + * @return array Complete query variables with undefined ones filled in empty.
792 + */
793 + public function fill_query_vars($array) {
794 + $keys = array(
795 + 'paged'
796 + , 's'
797 + , 'sentence'
798 + , 'fields'
799 + );
800 +
801 + foreach ( $keys as $key ) {
802 + if ( ! isset($array[$key]) )
803 + $array[$key] = '';
804 + }
805 +
806 + return $array;
807 + }
808 +
809 + /**
810 + * Generate SQL for the WHERE clause based on passed search terms.
811 + *
812 + * @since 1.0.0
813 + *
814 + * @param array $q Query variables.
815 + * @return string WHERE clause.
816 + */
817 + protected function parse_search( &$q ) {
818 + global $wpdb, $ct_table;
819 +
820 + $search = '';
821 +
822 + $search_fields = apply_filters( "ct_query_{$ct_table->name}_search_fields", array() );
823 +
824 + if( empty( $search_fields ) ) {
825 + return $search;
826 + }
827 +
828 + // added slashes screw with quote grouping when done early, so done later
829 + $q['s'] = stripslashes( $q['s'] );
830 +
831 + if ( empty( $_GET['s'] ) )
832 + $q['s'] = urldecode( $q['s'] );
833 + // there are no line breaks in <input /> fields
834 + $q['s'] = str_replace( array( "\r", "\n" ), '', $q['s'] );
835 + $q['search_terms_count'] = 1;
836 + if ( ! empty( $q['sentence'] ) ) {
837 + $q['search_terms'] = array( $q['s'] );
838 + } else {
839 + if ( preg_match_all( '/".*?("|$)|((?<=[\t ",+])|^)[^\t ",+]+/', $q['s'], $matches ) ) {
840 + $q['search_terms_count'] = count( $matches[0] );
841 + $q['search_terms'] = $this->parse_search_terms( $matches[0] );
842 + // if the search string has only short terms or stopwords, or is 10+ terms long, match it as sentence
843 + if ( empty( $q['search_terms'] ) || count( $q['search_terms'] ) > 9 )
844 + $q['search_terms'] = array( $q['s'] );
845 + } else {
846 + $q['search_terms'] = array( $q['s'] );
847 + }
848 + }
849 +
850 + $n = ! empty( $q['exact'] ) ? '' : '%';
851 + $searchand = '';
852 + $q['search_orderby_title'] = array();
853 +
854 + /**
855 + * Filters the prefix that indicates that a search term should be excluded from results.
856 + *
857 + * @since 1.0.0
858 + *
859 + * @param string $exclusion_prefix The prefix. Default '-'. Returning
860 + * an empty value disables exclusions.
861 + */
862 + $exclusion_prefix = apply_filters( 'ct_query_search_exclusion_prefix', '-' );
863 +
864 + foreach ( $q['search_terms'] as $term ) {
865 + // If there is an $exclusion_prefix, terms prefixed with it should be excluded.
866 + $exclude = $exclusion_prefix && ( $exclusion_prefix === substr( $term, 0, 1 ) );
867 + if ( $exclude ) {
868 + $like_op = 'NOT LIKE';
869 + $andor_op = 'AND';
870 + $term = substr( $term, 1 );
871 + } else {
872 + $like_op = 'LIKE';
873 + $andor_op = 'OR';
874 + }
875 +
876 + if ( $n && ! $exclude ) {
877 + $like = '%' . $wpdb->esc_like( $term ) . '%';
878 + //$q['search_orderby_title'][] = $wpdb->prepare( "{$wpdb->posts}.post_title LIKE %s", $like );
879 + }
880 +
881 + $like = $n . $wpdb->esc_like( $term ) . $n;
882 +
883 + $search_fields_where = array();
884 + $search_fields_args = array();
885 +
886 + foreach( $search_fields as $search_field ) {
887 + $search_fields_where[] = "{$ct_table->db->table_name}.$search_field $like_op %s";
888 + $search_fields_args[] = $like;
889 + }
890 +
891 + $search_where = "(" . implode( ") $andor_op (", $search_fields_where ) . ")";
892 +
893 + $search .= $wpdb->prepare( "{$searchand}($search_where)", $search_fields_args );
894 +
895 + //$search .= $wpdb->prepare( "{$searchand}(({$wpdb->posts}.post_title $like_op %s) $andor_op ({$wpdb->posts}.post_excerpt $like_op %s) $andor_op ({$wpdb->posts}.post_content $like_op %s))", $like, $like, $like );
896 +
897 + $searchand = ' AND ';
898 + }
899 +
900 + if ( ! empty( $search ) ) {
901 + $search = " AND ({$search}) ";
902 + }
903 +
904 + return $search;
905 + }
906 +
907 + /**
908 + * Check if the terms are suitable for searching.
909 + *
910 + * Uses an array of stopwords (terms) that are excluded from the separate
911 + * term matching when searching for posts. The list of English stopwords is
912 + * the approximate search engines list, and is translatable.
913 + *
914 + * @since 1.0.0
915 + *
916 + * @param array $terms Terms to check.
917 + * @return array Terms that are not stopwords.
918 + */
919 + protected function parse_search_terms( $terms ) {
920 + $strtolower = function_exists( 'mb_strtolower' ) ? 'mb_strtolower' : 'strtolower';
921 + $checked = array();
922 +
923 + $stopwords = $this->get_search_stopwords();
924 +
925 + foreach ( $terms as $term ) {
926 + // keep before/after spaces when term is for exact match
927 + if ( preg_match( '/^".+"$/', $term ) )
928 + $term = trim( $term, "\"'" );
929 + else
930 + $term = trim( $term, "\"' " );
931 +
932 + // Avoid single A-Z and single dashes.
933 + if ( ! $term || ( 1 === strlen( $term ) && preg_match( '/^[a-z\-]$/i', $term ) ) )
934 + continue;
935 +
936 + if ( in_array( call_user_func( $strtolower, $term ), $stopwords, true ) )
937 + continue;
938 +
939 + $checked[] = $term;
940 + }
941 +
942 + return $checked;
943 + }
944 +
945 + /**
946 + * Retrieve stopwords used when parsing search terms.
947 + *
948 + * @since 1.0.0
949 + *
950 + * @return array Stopwords.
951 + */
952 + protected function get_search_stopwords() {
953 + if ( isset( $this->stopwords ) )
954 + return $this->stopwords;
955 +
956 + /* translators: This is a comma-separated list of very common words that should be excluded from a search,
957 + * like a, an, and the. These are usually called "stopwords". You should not simply translate these individual
958 + * words into your language. Instead, look for and provide commonly accepted stopwords in your language.
959 + */
960 + $words = explode( ',', _x(
961 + 'about,an,are,as,at,be,by,com,for,from,how,in,is,it,of,on,or,that,the,this,to,was,what,when,where,who,will,with,www',
962 + 'Comma-separated list of search stopwords in your language'
963 + ) );
964 +
965 + $stopwords = array();
966 + foreach ( $words as $word ) {
967 + $word = trim( $word, "\r\n\t " );
968 + if ( $word )
969 + $stopwords[] = $word;
970 + }
971 +
972 + /**
973 + * Filters stopwords used when parsing search terms.
974 + *
975 + * @since 1.0.0
976 + *
977 + * @param array $stopwords Stopwords.
978 + */
979 + $this->stopwords = apply_filters( 'ct_search_stopwords', $stopwords );
980 + return $this->stopwords;
981 + }
982 +
983 + /**
984 + * Converts the given orderby alias (if allowed) to a properly-prefixed value.
985 + *
986 + * @since 4.0.0
987 + *
988 + * @global wpdb $wpdb WordPress database abstraction object.
989 + *
990 + * @param string $orderby Alias for the field to order by.
991 + * @return string|false Table-prefixed value to used in the ORDER clause. False otherwise.
992 + */
993 + protected function parse_orderby( $orderby ) {
994 + global $wpdb, $ct_table;
995 +
996 + $allowed_keys = array(
997 + 'rand' => 'RAND()',
998 + );
999 +
1000 +
1001 + foreach ($ct_table->db->schema->fields as $key => $value) {
1002 + $allowed_keys[$key] = "{$ct_table->db->table_name}.{$key}";
1003 + }
1004 +
1005 + $allowed_keys = apply_filters( "ct_query_{$ct_table->name}_orderby_allowed_keys", $allowed_keys, $orderby );
1006 +
1007 +
1008 + // If RAND() contains a seed value, sanitize and add to allowed keys.
1009 + if ( preg_match( '/RAND\(([0-9]+)\)/i', $orderby, $matches ) ) {
1010 + $orderby = 'rand_with_seed';
1011 + $allowed_keys['rand_with_seed'] = sprintf( 'RAND(%s)', (int) $matches[1] );
1012 + }
1013 +
1014 + if ( ! isset( $allowed_keys[$orderby] ) )
1015 + return '';
1016 +
1017 + $orderby_clause = $allowed_keys[$orderby];
1018 +
1019 + return apply_filters( "ct_query_{$ct_table->name}_parse_orderby", $orderby_clause, $orderby );
1020 +
1021 + }
1022 +
1023 + /**
1024 + * Parse an 'order' query variable and cast it to ASC or DESC as necessary.
1025 + *
1026 + * @since 1.0.0
1027 + * @access protected
1028 + *
1029 + * @param string $order The 'order' query variable.
1030 + * @return string The sanitized 'order' query variable.
1031 + */
1032 + protected function parse_order( $order ) {
1033 + if ( ! is_string( $order ) || empty( $order ) ) {
1034 + return 'DESC';
1035 + }
1036 +
1037 + if ( 'ASC' === strtoupper( $order ) ) {
1038 + return 'ASC';
1039 + } else {
1040 + return 'DESC';
1041 + }
1042 + }
1043 +
1044 + /**
1045 + * Set up the amount of found posts and the number of pages (if limit clause was used)
1046 + * for the current query.
1047 + *
1048 + * @since 1.0.0
1049 + * @access private
1050 + *
1051 + * @param array $q Query variables.
1052 + * @param string $limits LIMIT clauses of the query.
1053 + */
1054 + private function set_found_results( $q, $limits ) {
1055 + global $wpdb;
1056 + // Bail if posts is an empty array. Continue if posts is an empty string,
1057 + // null, or false to accommodate caching plugins that fill posts later.
1058 + if ( $q['no_found_rows'] || ( is_array( $this->results ) && ! $this->results ) )
1059 + return;
1060 +
1061 + if ( ! empty( $limits ) ) {
1062 + /**
1063 + * Filters the query to run for retrieving the found posts.
1064 + *
1065 + * @since 1.0.0
1066 + *
1067 + * @param string $found_posts The query to run to find the found posts.
1068 + * @param WP_Query &$this The WP_Query instance (passed by reference).
1069 + */
1070 + $this->found_results = $wpdb->get_var( apply_filters_ref_array( 'ct_found_results_query', array( 'SELECT FOUND_ROWS()', &$this ) ) );
1071 + } else {
1072 + $this->found_results = count( $this->results );
1073 + }
1074 +
1075 + /**
1076 + * Filters the number of found posts for the query.
1077 + *
1078 + * @since 1.0.0
1079 + *
1080 + * @param int $found_posts The number of posts found.
1081 + * @param WP_Query &$this The WP_Query instance (passed by reference).
1082 + */
1083 + $this->found_results = apply_filters_ref_array( 'ct_found_results', array( $this->found_results, &$this ) );
1084 +
1085 + if ( ! empty( $limits ) )
1086 + $this->max_num_pages = ceil( $this->found_results / $q['items_per_page'] );
1087 + }
1088 +
1089 + /**
1090 + * Sets up the query by parsing query string.
1091 + *
1092 + * @since 1.0.0
1093 + *
1094 + * @param string|array $query URL query string or array of query arguments.
1095 + * @return array List of results.
1096 + */
1097 + public function query( $query ) {
1098 + $this->init();
1099 + $this->query = $this->query_vars = wp_parse_args( $query );
1100 + return $this->get_results();
1101 + }
1102 +
1103 + }
1104 +
1105 +endif;