1<?php
2/**
3 * Query API: WP_Query class
4 *
5 * @package WordPress
6 * @subpackage Query
7 * @since 4.7.0
8 */
9
10/**
11 * The WordPress Query class.
12 *
13 * @link https://developer.wordpress.org/reference/classes/wp_query/
14 *
15 * @since 1.5.0
16 * @since 4.5.0 Removed the `$comments_popup` property.
17 */
18#[AllowDynamicProperties]
19class WP_Query {
20
21 /**
22 * Query vars set by the user.
23 *
24 * @since 1.5.0
25 * @var array
26 */
27 public $query;
28
29 /**
30 * Query vars, after parsing.
31 *
32 * @since 1.5.0
33 * @var array
34 */
35 public $query_vars = array();
36
37 /**
38 * Taxonomy query, as passed to get_tax_sql().
39 *
40 * @since 3.1.0
41 * @var WP_Tax_Query|null A taxonomy query instance.
42 */
43 public $tax_query;
44
45 /**
46 * Metadata query container.
47 *
48 * @since 3.2.0
49 * @var WP_Meta_Query A meta query instance.
50 */
51 public $meta_query = false;
52
53 /**
54 * Date query container.
55 *
56 * @since 3.7.0
57 * @var WP_Date_Query A date query instance.
58 */
59 public $date_query = false;
60
61 /**
62 * Holds the data for a single object that is queried.
63 *
64 * Holds the contents of a post, page, category, attachment.
65 *
66 * @since 1.5.0
67 * @var WP_Term|WP_Post_Type|WP_Post|WP_User|null
68 */
69 public $queried_object;
70
71 /**
72 * The ID of the queried object.
73 *
74 * @since 1.5.0
75 * @var int
76 */
77 public $queried_object_id;
78
79 /**
80 * SQL for the database query.
81 *
82 * @since 2.0.1
83 * @var string
84 */
85 public $request;
86
87 /**
88 * Array of post objects or post IDs.
89 *
90 * @since 1.5.0
91 * @var WP_Post[]|int[]
92 */
93 public $posts;
94
95 /**
96 * The number of posts for the current query.
97 *
98 * @since 1.5.0
99 * @var int
100 */
101 public $post_count = 0;
102
103 /**
104 * Index of the current item in the loop.
105 *
106 * @since 1.5.0
107 * @var int
108 */
109 public $current_post = -1;
110
111 /**
112 * Whether the caller is before the loop.
113 *
114 * @since 6.3.0
115 * @var bool
116 */
117 public $before_loop = true;
118
119 /**
120 * Whether the loop has started and the caller is in the loop.
121 *
122 * @since 2.0.0
123 * @var bool
124 */
125 public $in_the_loop = false;
126
127 /**
128 * The current post.
129 *
130 * This property does not get populated when the `fields` argument is set to
131 * `ids` or `id=>parent`.
132 *
133 * @since 1.5.0
134 * @var WP_Post|null
135 */
136 public $post;
137
138 /**
139 * The list of comments for current post.
140 *
141 * @since 2.2.0
142 * @var WP_Comment[]
143 */
144 public $comments;
145
146 /**
147 * The number of comments for the posts.
148 *
149 * @since 2.2.0
150 * @var int
151 */
152 public $comment_count = 0;
153
154 /**
155 * The index of the comment in the comment loop.
156 *
157 * @since 2.2.0
158 * @var int
159 */
160 public $current_comment = -1;
161
162 /**
163 * Current comment object.
164 *
165 * @since 2.2.0
166 * @var WP_Comment
167 */
168 public $comment;
169
170 /**
171 * The number of found posts for the current query.
172 *
173 * If limit clause was not used, equals $post_count.
174 *
175 * @since 2.1.0
176 * @var int
177 */
178 public $found_posts = 0;
179
180 /**
181 * The number of pages.
182 *
183 * @since 2.1.0
184 * @var int
185 */
186 public $max_num_pages = 0;
187
188 /**
189 * The number of comment pages.
190 *
191 * @since 2.7.0
192 * @var int
193 */
194 public $max_num_comment_pages = 0;
195
196 /**
197 * Signifies whether the current query is for a single post.
198 *
199 * @since 1.5.0
200 * @var bool
201 */
202 public $is_single = false;
203
204 /**
205 * Signifies whether the current query is for a preview.
206 *
207 * @since 2.0.0
208 * @var bool
209 */
210 public $is_preview = false;
211
212 /**
213 * Signifies whether the current query is for a page.
214 *
215 * @since 1.5.0
216 * @var bool
217 */
218 public $is_page = false;
219
220 /**
221 * Signifies whether the current query is for an archive.
222 *
223 * @since 1.5.0
224 * @var bool
225 */
226 public $is_archive = false;
227
228 /**
229 * Signifies whether the current query is for a date archive.
230 *
231 * @since 1.5.0
232 * @var bool
233 */
234 public $is_date = false;
235
236 /**
237 * Signifies whether the current query is for a year archive.
238 *
239 * @since 1.5.0
240 * @var bool
241 */
242 public $is_year = false;
243
244 /**
245 * Signifies whether the current query is for a month archive.
246 *
247 * @since 1.5.0
248 * @var bool
249 */
250 public $is_month = false;
251
252 /**
253 * Signifies whether the current query is for a day archive.
254 *
255 * @since 1.5.0
256 * @var bool
257 */
258 public $is_day = false;
259
260 /**
261 * Signifies whether the current query is for a specific time.
262 *
263 * @since 1.5.0
264 * @var bool
265 */
266 public $is_time = false;
267
268 /**
269 * Signifies whether the current query is for an author archive.
270 *
271 * @since 1.5.0
272 * @var bool
273 */
274 public $is_author = false;
275
276 /**
277 * Signifies whether the current query is for a category archive.
278 *
279 * @since 1.5.0
280 * @var bool
281 */
282 public $is_category = false;
283
284 /**
285 * Signifies whether the current query is for a tag archive.
286 *
287 * @since 2.3.0
288 * @var bool
289 */
290 public $is_tag = false;
291
292 /**
293 * Signifies whether the current query is for a taxonomy archive.
294 *
295 * @since 2.5.0
296 * @var bool
297 */
298 public $is_tax = false;
299
300 /**
301 * Signifies whether the current query is for a search.
302 *
303 * @since 1.5.0
304 * @var bool
305 */
306 public $is_search = false;
307
308 /**
309 * Signifies whether the current query is for a feed.
310 *
311 * @since 1.5.0
312 * @var bool
313 */
314 public $is_feed = false;
315
316 /**
317 * Signifies whether the current query is for a comment feed.
318 *
319 * @since 2.2.0
320 * @var bool
321 */
322 public $is_comment_feed = false;
323
324 /**
325 * Signifies whether the current query is for trackback endpoint call.
326 *
327 * @since 1.5.0
328 * @var bool
329 */
330 public $is_trackback = false;
331
332 /**
333 * Signifies whether the current query is for the site homepage.
334 *
335 * @since 1.5.0
336 * @var bool
337 */
338 public $is_home = false;
339
340 /**
341 * Signifies whether the current query is for the Privacy Policy page.
342 *
343 * @since 5.2.0
344 * @var bool
345 */
346 public $is_privacy_policy = false;
347
348 /**
349 * Signifies whether the current query couldn't find anything.
350 *
351 * @since 1.5.0
352 * @var bool
353 */
354 public $is_404 = false;
355
356 /**
357 * Signifies whether the current query is for an embed.
358 *
359 * @since 4.4.0
360 * @var bool
361 */
362 public $is_embed = false;
363
364 /**
365 * Signifies whether the current query is for a paged result and not for the first page.
366 *
367 * @since 1.5.0
368 * @var bool
369 */
370 public $is_paged = false;
371
372 /**
373 * Signifies whether the current query is for an administrative interface page.
374 *
375 * @since 1.5.0
376 * @var bool
377 */
378 public $is_admin = false;
379
380 /**
381 * Signifies whether the current query is for an attachment page.
382 *
383 * @since 2.0.0
384 * @var bool
385 */
386 public $is_attachment = false;
387
388 /**
389 * Signifies whether the current query is for an existing single post of any post type
390 * (post, attachment, page, custom post types).
391 *
392 * @since 2.1.0
393 * @var bool
394 */
395 public $is_singular = false;
396
397 /**
398 * Signifies whether the current query is for the robots.txt file.
399 *
400 * @since 2.1.0
401 * @var bool
402 */
403 public $is_robots = false;
404
405 /**
406 * Signifies whether the current query is for the favicon.ico file.
407 *
408 * @since 5.4.0
409 * @var bool
410 */
411 public $is_favicon = false;
412
413 /**
414 * Signifies whether the current query is for the page_for_posts page.
415 *
416 * Basically, the homepage if the option isn't set for the static homepage.
417 *
418 * @since 2.1.0
419 * @var bool
420 */
421 public $is_posts_page = false;
422
423 /**
424 * Signifies whether the current query is for a post type archive.
425 *
426 * @since 3.1.0
427 * @var bool
428 */
429 public $is_post_type_archive = false;
430
431 /**
432 * Stores the ->query_vars state like md5(serialize( $this->query_vars ) ) so we know
433 * whether we have to re-parse because something has changed
434 *
435 * @since 3.1.0
436 * @var bool|string
437 */
438 private $query_vars_hash = false;
439
440 /**
441 * Whether query vars have changed since the initial parse_query() call. Used to catch modifications to query vars made
442 * via pre_get_posts hooks.
443 *
444 * @since 3.1.1
445 * @var bool
446 */
447 private $query_vars_changed = true;
448
449 /**
450 * Set if post thumbnails are cached
451 *
452 * @since 3.2.0
453 * @var bool
454 */
455 public $thumbnails_cached = false;
456
457 /**
458 * Controls whether an attachment query should include filenames or not.
459 *
460 * @since 6.0.3
461 * @var bool
462 */
463 protected $allow_query_attachment_by_filename = false;
464
465 /**
466 * Cached list of search stopwords.
467 *
468 * @since 3.7.0
469 * @var array
470 */
471 private $stopwords;
472
473 private $compat_fields = array( 'query_vars_hash', 'query_vars_changed' );
474
475 private $compat_methods = array( 'init_query_flags', 'parse_tax_query' );
476
477 /**
478 * The cache key generated by the query.
479 *
480 * The cache key is generated by the method ::generate_cache_key() after the
481 * query has been normalized.
482 *
483 * @since 6.8.0
484 * @var string
485 */
486 private $query_cache_key = '';
487
488 /**
489 * Resets query flags to false.
490 *
491 * The query flags are what page info WordPress was able to figure out.
492 *
493 * @since 2.0.0
494 */
495 private function init_query_flags() {
496 $this->is_single = false;
497 $this->is_preview = false;
498 $this->is_page = false;
499 $this->is_archive = false;
500 $this->is_date = false;
501 $this->is_year = false;
502 $this->is_month = false;
503 $this->is_day = false;
504 $this->is_time = false;
505 $this->is_author = false;
506 $this->is_category = false;
507 $this->is_tag = false;
508 $this->is_tax = false;
509 $this->is_search = false;
510 $this->is_feed = false;
511 $this->is_comment_feed = false;
512 $this->is_trackback = false;
513 $this->is_home = false;
514 $this->is_privacy_policy = false;
515 $this->is_404 = false;
516 $this->is_paged = false;
517 $this->is_admin = false;
518 $this->is_attachment = false;
519 $this->is_singular = false;
520 $this->is_robots = false;
521 $this->is_favicon = false;
522 $this->is_posts_page = false;
523 $this->is_post_type_archive = false;
524 }
525
526 /**
527 * Initiates object properties and sets default values.
528 *
529 * @since 1.5.0
530 */
531 public function init() {
532 unset( $this->posts );
533 unset( $this->query );
534 $this->query_vars = array();
535 unset( $this->queried_object );
536 unset( $this->queried_object_id );
537 $this->post_count = 0;
538 $this->current_post = -1;
539 $this->in_the_loop = false;
540 $this->before_loop = true;
541 unset( $this->request );
542 unset( $this->post );
543 unset( $this->comments );
544 unset( $this->comment );
545 $this->comment_count = 0;
546 $this->current_comment = -1;
547 $this->found_posts = 0;
548 $this->max_num_pages = 0;
549 $this->max_num_comment_pages = 0;
550
551 $this->init_query_flags();
552 }
553
554 /**
555 * Reparses the query vars.
556 *
557 * @since 1.5.0
558 */
559 public function parse_query_vars() {
560 $this->parse_query();
561 }
562
563 /**
564 * Fills in the query variables, which do not exist within the parameter.
565 *
566 * @since 2.1.0
567 * @since 4.5.0 Removed the `comments_popup` public query variable.
568 *
569 * @param array $query_vars Defined query variables.
570 * @return array Complete query variables with undefined ones filled in empty.
571 */
572 public function fill_query_vars( $query_vars ) {
573 $keys = array(
574 'error',
575 'm',
576 'p',
577 'post_parent',
578 'subpost',
579 'subpost_id',
580 'attachment',
581 'attachment_id',
582 'name',
583 'pagename',
584 'page_id',
585 'second',
586 'minute',
587 'hour',
588 'day',
589 'monthnum',
590 'year',
591 'w',
592 'category_name',
593 'tag',
594 'cat',
595 'tag_id',
596 'author',
597 'author_name',
598 'feed',
599 'tb',
600 'paged',
601 'meta_key',
602 'meta_value',
603 'preview',
604 's',
605 'sentence',
606 'title',
607 'fields',
608 'menu_order',
609 'embed',
610 );
611
612 foreach ( $keys as $key ) {
613 if ( ! isset( $query_vars[ $key ] ) ) {
614 $query_vars[ $key ] = '';
615 }
616 }
617
618 $array_keys = array(
619 'category__in',
620 'category__not_in',
621 'category__and',
622 'post__in',
623 'post__not_in',
624 'post_name__in',
625 'tag__in',
626 'tag__not_in',
627 'tag__and',
628 'tag_slug__in',
629 'tag_slug__and',
630 'post_parent__in',
631 'post_parent__not_in',
632 'author__in',
633 'author__not_in',
634 'search_columns',
635 );
636
637 foreach ( $array_keys as $key ) {
638 if ( ! isset( $query_vars[ $key ] ) ) {
639 $query_vars[ $key ] = array();
640 }
641 }
642
643 return $query_vars;
644 }
645
646 /**
647 * Parses a query string and sets query type booleans.
648 *
649 * @since 1.5.0
650 * @since 4.2.0 Introduced the ability to order by specific clauses of a `$meta_query`, by passing the clause's
651 * array key to `$orderby`.
652 * @since 4.4.0 Introduced `$post_name__in` and `$title` parameters. `$s` was updated to support excluded
653 * search terms, by prepending a hyphen.
654 * @since 4.5.0 Removed the `$comments_popup` parameter.
655 * Introduced the `$comment_status` and `$ping_status` parameters.
656 * Introduced `RAND(x)` syntax for `$orderby`, which allows an integer seed value to random sorts.
657 * @since 4.6.0 Added 'post_name__in' support for `$orderby`. Introduced the `$lazy_load_term_meta` argument.
658 * @since 4.9.0 Introduced the `$comment_count` parameter.
659 * @since 5.1.0 Introduced the `$meta_compare_key` parameter.
660 * @since 5.3.0 Introduced the `$meta_type_key` parameter.
661 * @since 6.1.0 Introduced the `$update_menu_item_cache` parameter.
662 * @since 6.2.0 Introduced the `$search_columns` parameter.
663 *
664 * @param string|array $query {
665 * Optional. Array or string of Query parameters.
666 *
667 * @type int $attachment_id Attachment post ID. Used for 'attachment' post_type.
668 * @type int|string $author Author ID, or comma-separated list of IDs.
669 * @type string $author_name User 'user_nicename'.
670 * @type int[] $author__in An array of author IDs to query from.
671 * @type int[] $author__not_in An array of author IDs not to query from.
672 * @type bool $cache_results Whether to cache post information. Default true.
673 * @type int|string $cat Category ID or comma-separated list of IDs (this or any children).
674 * @type int[] $category__and An array of category IDs (AND in).
675 * @type int[] $category__in An array of category IDs (OR in, no children).
676 * @type int[] $category__not_in An array of category IDs (NOT in).
677 * @type string $category_name Use category slug (not name, this or any children).
678 * @type array|int $comment_count Filter results by comment count. Provide an integer to match
679 * comment count exactly. Provide an array with integer 'value'
680 * and 'compare' operator ('=', '!=', '>', '>=', '<', '<=' ) to
681 * compare against comment_count in a specific way.
682 * @type string $comment_status Comment status.
683 * @type int $comments_per_page The number of comments to return per page.
684 * Default 'comments_per_page' option.
685 * @type array $date_query An associative array of WP_Date_Query arguments.
686 * See WP_Date_Query::__construct().
687 * @type int $day Day of the month. Default empty. Accepts numbers 1-31.
688 * @type bool $exact Whether to search by exact keyword. Default false.
689 * @type string $fields Post fields to query for. Accepts:
690 * - '' Returns an array of complete post objects (`WP_Post[]`).
691 * - 'ids' Returns an array of post IDs (`int[]`).
692 * - 'id=>parent' Returns an associative array of parent post IDs,
693 * keyed by post ID (`int[]`).
694 * Default ''.
695 * @type int $hour Hour of the day. Default empty. Accepts numbers 0-23.
696 * @type int|bool $ignore_sticky_posts Whether to ignore sticky posts or not. Setting this to false
697 * excludes stickies from 'post__in'. Accepts 1|true, 0|false.
698 * Default false.
699 * @type int $m Combination YearMonth. Accepts any four-digit year and month
700 * numbers 01-12. Default empty.
701 * @type string|string[] $meta_key Meta key or keys to filter by.
702 * @type string|string[] $meta_value Meta value or values to filter by.
703 * @type string $meta_compare MySQL operator used for comparing the meta value.
704 * See WP_Meta_Query::__construct() for accepted values and default value.
705 * @type string $meta_compare_key MySQL operator used for comparing the meta key.
706 * See WP_Meta_Query::__construct() for accepted values and default value.
707 * @type string $meta_type MySQL data type that the meta_value column will be CAST to for comparisons.
708 * See WP_Meta_Query::__construct() for accepted values and default value.
709 * @type string $meta_type_key MySQL data type that the meta_key column will be CAST to for comparisons.
710 * See WP_Meta_Query::__construct() for accepted values and default value.
711 * @type array $meta_query An associative array of WP_Meta_Query arguments.
712 * See WP_Meta_Query::__construct() for accepted values.
713 * @type int $menu_order The menu order of the posts.
714 * @type int $minute Minute of the hour. Default empty. Accepts numbers 0-59.
715 * @type int $monthnum The two-digit month. Default empty. Accepts numbers 1-12.
716 * @type string $name Post slug.
717 * @type bool $nopaging Show all posts (true) or paginate (false). Default false.
718 * @type bool $no_found_rows Whether to skip counting the total rows found. Enabling can improve
719 * performance. Default false.
720 * @type int $offset The number of posts to offset before retrieval.
721 * @type string $order Designates ascending or descending order of posts. Default 'DESC'.
722 * Accepts 'ASC', 'DESC'.
723 * @type string|array $orderby Sort retrieved posts by parameter. One or more options may be passed.
724 * To use 'meta_value', or 'meta_value_num', 'meta_key=keyname' must be
725 * also be defined. To sort by a specific `$meta_query` clause, use that
726 * clause's array key. Accepts:
727 * - 'none'
728 * - 'name'
729 * - 'author'
730 * - 'date'
731 * - 'title'
732 * - 'modified'
733 * - 'menu_order'
734 * - 'parent'
735 * - 'ID'
736 * - 'rand'
737 * - 'relevance'
738 * - 'RAND(x)' (where 'x' is an integer seed value)
739 * - 'comment_count'
740 * - 'meta_value'
741 * - 'meta_value_num'
742 * - 'post__in'
743 * - 'post_name__in'
744 * - 'post_parent__in'
745 * - The array keys of `$meta_query`.
746 * Default is 'date', except when a search is being performed, when
747 * the default is 'relevance'.
748 * @type int $p Post ID.
749 * @type int $page Show the number of posts that would show up on page X of a
750 * static front page.
751 * @type int $paged The number of the current page.
752 * @type int $page_id Page ID.
753 * @type string $pagename Page slug.
754 * @type string $perm Show posts if user has the appropriate capability.
755 * @type string $ping_status Ping status.
756 * @type int[] $post__in An array of post IDs to retrieve, sticky posts will be included.
757 * @type int[] $post__not_in An array of post IDs not to retrieve. Note: a string of comma-
758 * separated IDs will NOT work.
759 * @type string $post_mime_type The mime type of the post. Used for 'attachment' post_type.
760 * @type string[] $post_name__in An array of post slugs that results must match.
761 * @type int $post_parent Page ID to retrieve child pages for. Use 0 to only retrieve
762 * top-level pages.
763 * @type int[] $post_parent__in An array containing parent page IDs to query child pages from.
764 * @type int[] $post_parent__not_in An array containing parent page IDs not to query child pages from.
765 * @type string|string[] $post_type A post type slug (string) or array of post type slugs.
766 * Default 'any' if using 'tax_query'.
767 * @type string|string[] $post_status A post status (string) or array of post statuses.
768 * @type int $posts_per_page The number of posts to query for. Use -1 to request all posts.
769 * @type int $posts_per_archive_page The number of posts to query for by archive page. Overrides
770 * 'posts_per_page' when is_archive(), or is_search() are true.
771 * @type string $s Search keyword(s). Prepending a term with a hyphen will
772 * exclude posts matching that term. Eg, 'pillow -sofa' will
773 * return posts containing 'pillow' but not 'sofa'. The
774 * character used for exclusion can be modified using the
775 * the 'wp_query_search_exclusion_prefix' filter.
776 * @type string[] $search_columns Array of column names to be searched. Accepts 'post_title',
777 * 'post_excerpt' and 'post_content'. Default empty array.
778 * @type int $second Second of the minute. Default empty. Accepts numbers 0-59.
779 * @type bool $sentence Whether to search by phrase. Default false.
780 * @type bool $suppress_filters Whether to suppress filters. Default false.
781 * @type string $tag Tag slug. Comma-separated (either), Plus-separated (all).
782 * @type int[] $tag__and An array of tag IDs (AND in).
783 * @type int[] $tag__in An array of tag IDs (OR in).
784 * @type int[] $tag__not_in An array of tag IDs (NOT in).
785 * @type int $tag_id Tag id or comma-separated list of IDs.
786 * @type string[] $tag_slug__and An array of tag slugs (AND in).
787 * @type string[] $tag_slug__in An array of tag slugs (OR in). unless 'ignore_sticky_posts' is
788 * true. Note: a string of comma-separated IDs will NOT work.
789 * @type array $tax_query An associative array of WP_Tax_Query arguments.
790 * See WP_Tax_Query::__construct().
791 * @type string $title Post title.
792 * @type bool $update_post_meta_cache Whether to update the post meta cache. Default true.
793 * @type bool $update_post_term_cache Whether to update the post term cache. Default true.
794 * @type bool $update_menu_item_cache Whether to update the menu item cache. Default false.
795 * @type bool $lazy_load_term_meta Whether to lazy-load term meta. Setting to false will
796 * disable cache priming for term meta, so that each
797 * get_term_meta() call will hit the database.
798 * Defaults to the value of `$update_post_term_cache`.
799 * @type int $w The week number of the year. Default empty. Accepts numbers 0-53.
800 * @type int $year The four-digit year. Default empty. Accepts any four-digit year.
801 * }
802 */
803 public function parse_query( $query = '' ) {
804 if ( ! empty( $query ) ) {
805 $this->init();
806 $this->query = wp_parse_args( $query );
807 $this->query_vars = $this->query;
808 } elseif ( ! isset( $this->query ) ) {
809 $this->query = $this->query_vars;
810 }
811
812 $this->query_vars = $this->fill_query_vars( $this->query_vars );
813 $query_vars = &$this->query_vars;
814 $this->query_vars_changed = true;
815
816 if ( ! empty( $query_vars['robots'] ) ) {
817 $this->is_robots = true;
818 } elseif ( ! empty( $query_vars['favicon'] ) ) {
819 $this->is_favicon = true;
820 }
821
822 if ( ! is_scalar( $query_vars['p'] ) || (int) $query_vars['p'] < 0 ) {
823 $query_vars['p'] = 0;
824 $query_vars['error'] = '404';
825 } else {
826 $query_vars['p'] = (int) $query_vars['p'];
827 }
828
829 $query_vars['page_id'] = is_scalar( $query_vars['page_id'] ) ? absint( $query_vars['page_id'] ) : 0;
830 $query_vars['year'] = is_scalar( $query_vars['year'] ) ? absint( $query_vars['year'] ) : 0;
831 $query_vars['monthnum'] = is_scalar( $query_vars['monthnum'] ) ? absint( $query_vars['monthnum'] ) : 0;
832 $query_vars['day'] = is_scalar( $query_vars['day'] ) ? absint( $query_vars['day'] ) : 0;
833 $query_vars['w'] = is_scalar( $query_vars['w'] ) ? absint( $query_vars['w'] ) : 0;
834 $query_vars['m'] = is_scalar( $query_vars['m'] ) ? preg_replace( '|[^0-9]|', '', $query_vars['m'] ) : '';
835 $query_vars['paged'] = is_scalar( $query_vars['paged'] ) ? absint( $query_vars['paged'] ) : 0;
836 $query_vars['cat'] = preg_replace( '|[^0-9,-]|', '', $query_vars['cat'] ); // Array or comma-separated list of positive or negative integers.
837 $query_vars['author'] = is_scalar( $query_vars['author'] ) ? preg_replace( '|[^0-9,-]|', '', $query_vars['author'] ) : ''; // Comma-separated list of positive or negative integers.
838 $query_vars['pagename'] = is_scalar( $query_vars['pagename'] ) ? trim( $query_vars['pagename'] ) : '';
839 $query_vars['name'] = is_scalar( $query_vars['name'] ) ? trim( $query_vars['name'] ) : '';
840 $query_vars['title'] = is_scalar( $query_vars['title'] ) ? trim( $query_vars['title'] ) : '';
841
842 if ( is_scalar( $query_vars['hour'] ) && '' !== $query_vars['hour'] ) {
843 $query_vars['hour'] = absint( $query_vars['hour'] );
844 } else {
845 $query_vars['hour'] = '';
846 }
847
848 if ( is_scalar( $query_vars['minute'] ) && '' !== $query_vars['minute'] ) {
849 $query_vars['minute'] = absint( $query_vars['minute'] );
850 } else {
851 $query_vars['minute'] = '';
852 }
853
854 if ( is_scalar( $query_vars['second'] ) && '' !== $query_vars['second'] ) {
855 $query_vars['second'] = absint( $query_vars['second'] );
856 } else {
857 $query_vars['second'] = '';
858 }
859
860 if ( is_scalar( $query_vars['menu_order'] ) && '' !== $query_vars['menu_order'] ) {
861 $query_vars['menu_order'] = absint( $query_vars['menu_order'] );
862 } else {
863 $query_vars['menu_order'] = '';
864 }
865
866 // Fairly large, potentially too large, upper bound for search string lengths.
867 if ( ! is_scalar( $query_vars['s'] ) || ( ! empty( $query_vars['s'] ) && strlen( $query_vars['s'] ) > 1600 ) ) {
868 $query_vars['s'] = '';
869 }
870
871 // Compat. Map subpost to attachment.
872 if ( is_scalar( $query_vars['subpost'] ) && '' != $query_vars['subpost'] ) {
873 $query_vars['attachment'] = $query_vars['subpost'];
874 }
875 if ( is_scalar( $query_vars['subpost_id'] ) && '' != $query_vars['subpost_id'] ) {
876 $query_vars['attachment_id'] = $query_vars['subpost_id'];
877 }
878
879 $query_vars['attachment_id'] = is_scalar( $query_vars['attachment_id'] ) ? absint( $query_vars['attachment_id'] ) : 0;
880
881 if ( ( '' !== $query_vars['attachment'] ) || ! empty( $query_vars['attachment_id'] ) ) {
882 $this->is_single = true;
883 $this->is_attachment = true;
884 } elseif ( '' !== $query_vars['name'] ) {
885 $this->is_single = true;
886 } elseif ( $query_vars['p'] ) {
887 $this->is_single = true;
888 } elseif ( '' !== $query_vars['pagename'] || ! empty( $query_vars['page_id'] ) ) {
889 $this->is_page = true;
890 $this->is_single = false;
891 } else {
892 // Look for archive queries. Dates, categories, authors, search, post type archives.
893
894 if ( isset( $this->query['s'] ) ) {
895 $this->is_search = true;
896 }
897
898 if ( '' !== $query_vars['second'] ) {
899 $this->is_time = true;
900 $this->is_date = true;
901 }
902
903 if ( '' !== $query_vars['minute'] ) {
904 $this->is_time = true;
905 $this->is_date = true;
906 }
907
908 if ( '' !== $query_vars['hour'] ) {
909 $this->is_time = true;
910 $this->is_date = true;
911 }
912
913 if ( $query_vars['day'] ) {
914 if ( ! $this->is_date ) {
915 $date = sprintf( '%04d-%02d-%02d', $query_vars['year'], $query_vars['monthnum'], $query_vars['day'] );
916 if ( $query_vars['monthnum'] && $query_vars['year'] && ! wp_checkdate( $query_vars['monthnum'], $query_vars['day'], $query_vars['year'], $date ) ) {
917 $query_vars['error'] = '404';
918 } else {
919 $this->is_day = true;
920 $this->is_date = true;
921 }
922 }
923 }
924
925 if ( $query_vars['monthnum'] ) {
926 if ( ! $this->is_date ) {
927 if ( 12 < $query_vars['monthnum'] ) {
928 $query_vars['error'] = '404';
929 } else {
930 $this->is_month = true;
931 $this->is_date = true;
932 }
933 }
934 }
935
936 if ( $query_vars['year'] ) {
937 if ( ! $this->is_date ) {
938 $this->is_year = true;
939 $this->is_date = true;
940 }
941 }
942
943 if ( $query_vars['m'] ) {
944 $this->is_date = true;
945 if ( strlen( $query_vars['m'] ) > 9 ) {
946 $this->is_time = true;
947 } elseif ( strlen( $query_vars['m'] ) > 7 ) {
948 $this->is_day = true;
949 } elseif ( strlen( $query_vars['m'] ) > 5 ) {
950 $this->is_month = true;
951 } else {
952 $this->is_year = true;
953 }
954 }
955
956 if ( $query_vars['w'] ) {
957 $this->is_date = true;
958 }
959
960 $this->query_vars_hash = false;
961 $this->parse_tax_query( $query_vars );
962
963 foreach ( $this->tax_query->queries as $tax_query ) {
964 if ( ! is_array( $tax_query ) ) {
965 continue;
966 }
967
968 if ( isset( $tax_query['operator'] ) && 'NOT IN' !== $tax_query['operator'] ) {
969 switch ( $tax_query['taxonomy'] ) {
970 case 'category':
971 $this->is_category = true;
972 break;
973 case 'post_tag':
974 $this->is_tag = true;
975 break;
976 default:
977 $this->is_tax = true;
978 }
979 }
980 }
981 unset( $tax_query );
982
983 if ( empty( $query_vars['author'] ) || ( '0' == $query_vars['author'] ) ) {
984 $this->is_author = false;
985 } else {
986 $this->is_author = true;
987 }
988
989 if ( '' !== $query_vars['author_name'] ) {
990 $this->is_author = true;
991 }
992
993 if ( ! empty( $query_vars['post_type'] ) && ! is_array( $query_vars['post_type'] ) ) {
994 $post_type_obj = get_post_type_object( $query_vars['post_type'] );
995 if ( ! empty( $post_type_obj->has_archive ) ) {
996 $this->is_post_type_archive = true;
997 }
998 }
999
1000 if ( $this->is_post_type_archive || $this->is_date || $this->is_author || $this->is_category || $this->is_tag || $this->is_tax ) {
1001 $this->is_archive = true;
1002 }
1003 }
1004
1005 if ( '' != $query_vars['feed'] ) {
1006 $this->is_feed = true;
1007 }
1008
1009 if ( '' != $query_vars['embed'] ) {
1010 $this->is_embed = true;
1011 }
1012
1013 if ( '' != $query_vars['tb'] ) {
1014 $this->is_trackback = true;
1015 }
1016
1017 if ( '' != $query_vars['paged'] && ( (int) $query_vars['paged'] > 1 ) ) {
1018 $this->is_paged = true;
1019 }
1020
1021 // If we're previewing inside the write screen.
1022 if ( '' != $query_vars['preview'] ) {
1023 $this->is_preview = true;
1024 }
1025
1026 if ( is_admin() ) {
1027 $this->is_admin = true;
1028 }
1029
1030 if ( str_contains( $query_vars['feed'], 'comments-' ) ) {
1031 $query_vars['feed'] = str_replace( 'comments-', '', $query_vars['feed'] );
1032 $query_vars['withcomments'] = 1;
1033 }
1034
1035 $this->is_singular = $this->is_single || $this->is_page || $this->is_attachment;
1036
1037 if ( $this->is_feed && ( ! empty( $query_vars['withcomments'] ) || ( empty( $query_vars['withoutcomments'] ) && $this->is_singular ) ) ) {
1038 $this->is_comment_feed = true;
1039 }
1040
1041 if ( ! ( $this->is_singular || $this->is_archive || $this->is_search || $this->is_feed
1042 || ( wp_is_serving_rest_request() && $this->is_main_query() )
1043 || $this->is_trackback || $this->is_404 || $this->is_admin || $this->is_robots || $this->is_favicon ) ) {
1044 $this->is_home = true;
1045 }
1046
1047 // Correct `is_*` for 'page_on_front' and 'page_for_posts'.
1048 if ( $this->is_home && 'page' === get_option( 'show_on_front' ) && get_option( 'page_on_front' ) ) {
1049 $_query = wp_parse_args( $this->query );
1050 // 'pagename' can be set and empty depending on matched rewrite rules. Ignore an empty 'pagename'.
1051 if ( isset( $_query['pagename'] ) && '' === $_query['pagename'] ) {
1052 unset( $_query['pagename'] );
1053 }
1054
1055 unset( $_query['embed'] );
1056
1057 if ( empty( $_query ) || ! array_diff( array_keys( $_query ), array( 'preview', 'page', 'paged', 'cpage' ) ) ) {
1058 $this->is_page = true;
1059 $this->is_home = false;
1060 $query_vars['page_id'] = get_option( 'page_on_front' );
1061 // Correct <!--nextpage--> for 'page_on_front'.
1062 if ( ! empty( $query_vars['paged'] ) ) {
1063 $query_vars['page'] = $query_vars['paged'];
1064 unset( $query_vars['paged'] );
1065 }
1066 }
1067 }
1068
1069 if ( '' !== $query_vars['pagename'] ) {
1070 $this->queried_object = get_page_by_path( $query_vars['pagename'] );
1071
1072 if ( $this->queried_object && 'attachment' === $this->queried_object->post_type ) {
1073 if ( preg_match( '/^[^%]*%(?:postname)%/', get_option( 'permalink_structure' ) ) ) {
1074 // See if we also have a post with the same slug.
1075 $post = get_page_by_path( $query_vars['pagename'], OBJECT, 'post' );
1076 if ( $post ) {
1077 $this->queried_object = $post;
1078 $this->is_page = false;
1079 $this->is_single = true;
1080 }
1081 }
1082 }
1083
1084 if ( ! empty( $this->queried_object ) ) {
1085 $this->queried_object_id = (int) $this->queried_object->ID;
1086 } else {
1087 unset( $this->queried_object );
1088 }
1089
1090 if ( 'page' === get_option( 'show_on_front' ) && isset( $this->queried_object_id ) && get_option( 'page_for_posts' ) == $this->queried_object_id ) {
1091 $this->is_page = false;
1092 $this->is_home = true;
1093 $this->is_posts_page = true;
1094 }
1095
1096 if ( isset( $this->queried_object_id ) && get_option( 'wp_page_for_privacy_policy' ) == $this->queried_object_id ) {
1097 $this->is_privacy_policy = true;
1098 }
1099 }
1100
1101 if ( $query_vars['page_id'] ) {
1102 if ( 'page' === get_option( 'show_on_front' ) && get_option( 'page_for_posts' ) == $query_vars['page_id'] ) {
1103 $this->is_page = false;
1104 $this->is_home = true;
1105 $this->is_posts_page = true;
1106 }
1107
1108 if ( get_option( 'wp_page_for_privacy_policy' ) == $query_vars['page_id'] ) {
1109 $this->is_privacy_policy = true;
1110 }
1111 }
1112
1113 if ( ! empty( $query_vars['post_type'] ) ) {
1114 if ( is_array( $query_vars['post_type'] ) ) {
1115 $query_vars['post_type'] = array_map( 'sanitize_key', array_unique( $query_vars['post_type'] ) );
1116 sort( $query_vars['post_type'] );
1117 } else {
1118 $query_vars['post_type'] = sanitize_key( $query_vars['post_type'] );
1119 }
1120 }
1121
1122 if ( ! empty( $query_vars['post_status'] ) ) {
1123 if ( is_array( $query_vars['post_status'] ) ) {
1124 $query_vars['post_status'] = array_map( 'sanitize_key', array_unique( $query_vars['post_status'] ) );
1125 sort( $query_vars['post_status'] );
1126 } else {
1127 $query_vars['post_status'] = preg_replace( '|[^a-z0-9_,-]|', '', $query_vars['post_status'] );
1128 }
1129 }
1130
1131 if ( $this->is_posts_page && ( ! isset( $query_vars['withcomments'] ) || ! $query_vars['withcomments'] ) ) {
1132 $this->is_comment_feed = false;
1133 }
1134
1135 $this->is_singular = $this->is_single || $this->is_page || $this->is_attachment;
1136 // Done correcting `is_*` for 'page_on_front' and 'page_for_posts'.
1137
1138 if ( '404' == $query_vars['error'] ) {
1139 $this->set_404();
1140 }
1141
1142 $this->is_embed = $this->is_embed && ( $this->is_singular || $this->is_404 );
1143
1144 $this->query_vars_hash = md5( serialize( $this->query_vars ) );
1145 $this->query_vars_changed = false;
1146
1147 /**
1148 * Fires after the main query vars have been parsed.
1149 *
1150 * @since 1.5.0
1151 *
1152 * @param WP_Query $query The WP_Query instance (passed by reference).
1153 */
1154 do_action_ref_array( 'parse_query', array( &$this ) );
1155 }
1156
1157 /**
1158 * Parses various taxonomy related query vars.
1159 *
1160 * For BC, this method is not marked as protected. See [28987].
1161 *
1162 * @since 3.1.0
1163 *
1164 * @param array $query_vars The query variables. Passed by reference.
1165 */
1166 public function parse_tax_query( &$query_vars ) {
1167 if ( ! empty( $query_vars['tax_query'] ) && is_array( $query_vars['tax_query'] ) ) {
1168 $tax_query = $query_vars['tax_query'];
1169 } else {
1170 $tax_query = array();
1171 }
1172
1173 if ( ! empty( $query_vars['taxonomy'] ) && ! empty( $query_vars['term'] ) ) {
1174 $tax_query[] = array(
1175 'taxonomy' => $query_vars['taxonomy'],
1176 'terms' => array( $query_vars['term'] ),
1177 'field' => 'slug',
1178 );
1179 }
1180
1181 foreach ( get_taxonomies( array(), 'objects' ) as $taxonomy => $t ) {
1182 if ( 'post_tag' === $taxonomy ) {
1183 continue; // Handled further down in the $query_vars['tag'] block.
1184 }
1185
1186 if ( $t->query_var && ! empty( $query_vars[ $t->query_var ] ) ) {
1187 $tax_query_defaults = array(
1188 'taxonomy' => $taxonomy,
1189 'field' => 'slug',
1190 );
1191
1192 if ( ! empty( $t->rewrite['hierarchical'] ) ) {
1193 $query_vars[ $t->query_var ] = wp_basename( $query_vars[ $t->query_var ] );
1194 }
1195
1196 $term = $query_vars[ $t->query_var ];
1197
1198 if ( ! is_array( $term ) ) {
1199 $term = explode( ',', $term );
1200 $term = array_map( 'trim', $term );
1201 }
1202 sort( $term );
1203 $term = implode( ',', $term );
1204
1205 if ( str_contains( $term, '+' ) ) {
1206 $terms = preg_split( '/[+]+/', $term );
1207 foreach ( $terms as $term ) {
1208 $tax_query[] = array_merge(
1209 $tax_query_defaults,
1210 array(
1211 'terms' => array( $term ),
1212 )
1213 );
1214 }
1215 } else {
1216 $tax_query[] = array_merge(
1217 $tax_query_defaults,
1218 array(
1219 'terms' => preg_split( '/[,]+/', $term ),
1220 )
1221 );
1222 }
1223 }
1224 }
1225
1226 // If query string 'cat' is an array, implode it.
1227 if ( is_array( $query_vars['cat'] ) ) {
1228 $query_vars['cat'] = implode( ',', $query_vars['cat'] );
1229 }
1230
1231 // Category stuff.
1232
1233 if ( ! empty( $query_vars['cat'] ) && ! $this->is_singular ) {
1234 $cat_in = array();
1235 $cat_not_in = array();
1236
1237 $cat_array = preg_split( '/[,\s]+/', urldecode( $query_vars['cat'] ) );
1238 $cat_array = array_map( 'intval', $cat_array );
1239 sort( $cat_array );
1240 $query_vars['cat'] = implode( ',', $cat_array );
1241
1242 foreach ( $cat_array as $cat ) {
1243 if ( $cat > 0 ) {
1244 $cat_in[] = $cat;
1245 } elseif ( $cat < 0 ) {
1246 $cat_not_in[] = abs( $cat );
1247 }
1248 }
1249
1250 if ( ! empty( $cat_in ) ) {
1251 $tax_query[] = array(
1252 'taxonomy' => 'category',
1253 'terms' => $cat_in,
1254 'field' => 'term_id',
1255 'include_children' => true,
1256 );
1257 }
1258
1259 if ( ! empty( $cat_not_in ) ) {
1260 $tax_query[] = array(
1261 'taxonomy' => 'category',
1262 'terms' => $cat_not_in,
1263 'field' => 'term_id',
1264 'operator' => 'NOT IN',
1265 'include_children' => true,
1266 );
1267 }
1268 unset( $cat_array, $cat_in, $cat_not_in );
1269 }
1270
1271 if ( ! empty( $query_vars['category__and'] ) && 1 === count( (array) $query_vars['category__and'] ) ) {
1272 $query_vars['category__and'] = (array) $query_vars['category__and'];
1273 if ( ! isset( $query_vars['category__in'] ) ) {
1274 $query_vars['category__in'] = array();
1275 }
1276 $query_vars['category__in'][] = absint( reset( $query_vars['category__and'] ) );
1277 unset( $query_vars['category__and'] );
1278 }
1279
1280 if ( ! empty( $query_vars['category__in'] ) ) {
1281 $query_vars['category__in'] = array_map( 'absint', array_unique( (array) $query_vars['category__in'] ) );
1282 sort( $query_vars['category__in'] );
1283 $tax_query[] = array(
1284 'taxonomy' => 'category',
1285 'terms' => $query_vars['category__in'],
1286 'field' => 'term_id',
1287 'include_children' => false,
1288 );
1289 }
1290
1291 if ( ! empty( $query_vars['category__not_in'] ) ) {
1292 $query_vars['category__not_in'] = array_map( 'absint', array_unique( (array) $query_vars['category__not_in'] ) );
1293 sort( $query_vars['category__not_in'] );
1294 $tax_query[] = array(
1295 'taxonomy' => 'category',
1296 'terms' => $query_vars['category__not_in'],
1297 'operator' => 'NOT IN',
1298 'include_children' => false,
1299 );
1300 }
1301
1302 if ( ! empty( $query_vars['category__and'] ) ) {
1303 $query_vars['category__and'] = array_map( 'absint', array_unique( (array) $query_vars['category__and'] ) );
1304 sort( $query_vars['category__and'] );
1305 $tax_query[] = array(
1306 'taxonomy' => 'category',
1307 'terms' => $query_vars['category__and'],
1308 'field' => 'term_id',
1309 'operator' => 'AND',
1310 'include_children' => false,
1311 );
1312 }
1313
1314 // If query string 'tag' is array, implode it.
1315 if ( is_array( $query_vars['tag'] ) ) {
1316 $query_vars['tag'] = implode( ',', $query_vars['tag'] );
1317 }
1318
1319 // Tag stuff.
1320
1321 if ( '' !== $query_vars['tag'] && ! $this->is_singular && $this->query_vars_changed ) {
1322 if ( str_contains( $query_vars['tag'], ',' ) ) {
1323 // @todo Handle normalizing `tag` query string.
1324 $tags = preg_split( '/[,\r\n\t ]+/', $query_vars['tag'] );
1325 foreach ( (array) $tags as $tag ) {
1326 $tag = sanitize_term_field( 'slug', $tag, 0, 'post_tag', 'db' );
1327 $query_vars['tag_slug__in'][] = $tag;
1328 sort( $query_vars['tag_slug__in'] );
1329 }
1330 } elseif ( preg_match( '/[+\r\n\t ]+/', $query_vars['tag'] ) || ! empty( $query_vars['cat'] ) ) {
1331 $tags = preg_split( '/[+\r\n\t ]+/', $query_vars['tag'] );
1332 foreach ( (array) $tags as $tag ) {
1333 $tag = sanitize_term_field( 'slug', $tag, 0, 'post_tag', 'db' );
1334 $query_vars['tag_slug__and'][] = $tag;
1335 }
1336 } else {
1337 $query_vars['tag'] = sanitize_term_field( 'slug', $query_vars['tag'], 0, 'post_tag', 'db' );
1338 $query_vars['tag_slug__in'][] = $query_vars['tag'];
1339 sort( $query_vars['tag_slug__in'] );
1340 }
1341 }
1342
1343 if ( ! empty( $query_vars['tag_id'] ) ) {
1344 $query_vars['tag_id'] = absint( $query_vars['tag_id'] );
1345 $tax_query[] = array(
1346 'taxonomy' => 'post_tag',
1347 'terms' => $query_vars['tag_id'],
1348 );
1349 }
1350
1351 if ( ! empty( $query_vars['tag__in'] ) ) {
1352 $query_vars['tag__in'] = array_map( 'absint', array_unique( (array) $query_vars['tag__in'] ) );
1353 sort( $query_vars['tag__in'] );
1354 $tax_query[] = array(
1355 'taxonomy' => 'post_tag',
1356 'terms' => $query_vars['tag__in'],
1357 );
1358 }
1359
1360 if ( ! empty( $query_vars['tag__not_in'] ) ) {
1361 $query_vars['tag__not_in'] = array_map( 'absint', array_unique( (array) $query_vars['tag__not_in'] ) );
1362 sort( $query_vars['tag__not_in'] );
1363 $tax_query[] = array(
1364 'taxonomy' => 'post_tag',
1365 'terms' => $query_vars['tag__not_in'],
1366 'operator' => 'NOT IN',
1367 );
1368 }
1369
1370 if ( ! empty( $query_vars['tag__and'] ) ) {
1371 $query_vars['tag__and'] = array_map( 'absint', array_unique( (array) $query_vars['tag__and'] ) );
1372 sort( $query_vars['tag__and'] );
1373 $tax_query[] = array(
1374 'taxonomy' => 'post_tag',
1375 'terms' => $query_vars['tag__and'],
1376 'operator' => 'AND',
1377 );
1378 }
1379
1380 if ( ! empty( $query_vars['tag_slug__in'] ) ) {
1381 $query_vars['tag_slug__in'] = array_map( 'sanitize_title_for_query', array_unique( (array) $query_vars['tag_slug__in'] ) );
1382 sort( $query_vars['tag_slug__in'] );
1383 $tax_query[] = array(
1384 'taxonomy' => 'post_tag',
1385 'terms' => $query_vars['tag_slug__in'],
1386 'field' => 'slug',
1387 );
1388 }
1389
1390 if ( ! empty( $query_vars['tag_slug__and'] ) ) {
1391 $query_vars['tag_slug__and'] = array_map( 'sanitize_title_for_query', array_unique( (array) $query_vars['tag_slug__and'] ) );
1392 sort( $query_vars['tag_slug__and'] );
1393 $tax_query[] = array(
1394 'taxonomy' => 'post_tag',
1395 'terms' => $query_vars['tag_slug__and'],
1396 'field' => 'slug',
1397 'operator' => 'AND',
1398 );
1399 }
1400
1401 $this->tax_query = new WP_Tax_Query( $tax_query );
1402
1403 /**
1404 * Fires after taxonomy-related query vars have been parsed.
1405 *
1406 * @since 3.7.0
1407 *
1408 * @param WP_Query $query The WP_Query instance.
1409 */
1410 do_action( 'parse_tax_query', $this );
1411 }
1412
1413 /**
1414 * Generates SQL for the WHERE clause based on passed search terms.
1415 *
1416 * @since 3.7.0
1417 *
1418 * @global wpdb $wpdb WordPress database abstraction object.
1419 *
1420 * @param array $query_vars Query variables.
1421 * @return string WHERE clause.
1422 */
1423 protected function parse_search( &$query_vars ) {
1424 global $wpdb;
1425
1426 $search = '';
1427
1428 // Added slashes screw with quote grouping when done early, so done later.
1429 $query_vars['s'] = stripslashes( $query_vars['s'] );
1430 if ( empty( $_GET['s'] ) && $this->is_main_query() ) {
1431 $query_vars['s'] = urldecode( $query_vars['s'] );
1432 }
1433 // There are no line breaks in <input /> fields.
1434 $query_vars['s'] = str_replace( array( "\r", "\n" ), '', $query_vars['s'] );
1435 $query_vars['search_terms_count'] = 1;
1436 if ( ! empty( $query_vars['sentence'] ) ) {
1437 $query_vars['search_terms'] = array( $query_vars['s'] );
1438 } else {
1439 if ( preg_match_all( '/".*?("|$)|((?<=[\t ",+])|^)[^\t ",+]+/', $query_vars['s'], $matches ) ) {
1440 $query_vars['search_terms_count'] = count( $matches[0] );
1441 $query_vars['search_terms'] = $this->parse_search_terms( $matches[0] );
1442 // If the search string has only short terms or stopwords, or is 10+ terms long, match it as sentence.
1443 if ( empty( $query_vars['search_terms'] ) || count( $query_vars['search_terms'] ) > 9 ) {
1444 $query_vars['search_terms'] = array( $query_vars['s'] );
1445 }
1446 } else {
1447 $query_vars['search_terms'] = array( $query_vars['s'] );
1448 }
1449 }
1450
1451 $n = ! empty( $query_vars['exact'] ) ? '' : '%';
1452 $searchand = '';
1453 $query_vars['search_orderby_title'] = array();
1454
1455 $default_search_columns = array( 'post_title', 'post_excerpt', 'post_content' );
1456 $search_columns = ! empty( $query_vars['search_columns'] ) ? $query_vars['search_columns'] : $default_search_columns;
1457 if ( ! is_array( $search_columns ) ) {
1458 $search_columns = array( $search_columns );
1459 }
1460
1461 /**
1462 * Filters the columns to search in a WP_Query search.
1463 *
1464 * The supported columns are `post_title`, `post_excerpt` and `post_content`.
1465 * They are all included by default.
1466 *
1467 * @since 6.2.0
1468 *
1469 * @param string[] $search_columns Array of column names to be searched.
1470 * @param string $search Text being searched.
1471 * @param WP_Query $query The current WP_Query instance.
1472 */
1473 $search_columns = (array) apply_filters( 'post_search_columns', $search_columns, $query_vars['s'], $this );
1474
1475 // Use only supported search columns.
1476 $search_columns = array_intersect( $search_columns, $default_search_columns );
1477 if ( empty( $search_columns ) ) {
1478 $search_columns = $default_search_columns;
1479 }
1480
1481 /**
1482 * Filters the prefix that indicates that a search term should be excluded from results.
1483 *
1484 * @since 4.7.0
1485 *
1486 * @param string $exclusion_prefix The prefix. Default '-'. Returning
1487 * an empty value disables exclusions.
1488 */
1489 $exclusion_prefix = apply_filters( 'wp_query_search_exclusion_prefix', '-' );
1490
1491 foreach ( $query_vars['search_terms'] as $term ) {
1492 // If there is an $exclusion_prefix, terms prefixed with it should be excluded.
1493 $exclude = $exclusion_prefix && str_starts_with( $term, $exclusion_prefix );
1494 if ( $exclude ) {
1495 $like_op = 'NOT LIKE';
1496 $andor_op = 'AND';
1497 $term = substr( $term, 1 );
1498 } else {
1499 $like_op = 'LIKE';
1500 $andor_op = 'OR';
1501 }
1502
1503 if ( $n && ! $exclude ) {
1504 $like = '%' . $wpdb->esc_like( $term ) . '%';
1505 $query_vars['search_orderby_title'][] = $wpdb->prepare( "{$wpdb->posts}.post_title LIKE %s", $like );
1506 }
1507
1508 $like = $n . $wpdb->esc_like( $term ) . $n;
1509
1510 $search_columns_parts = array();
1511 foreach ( $search_columns as $search_column ) {
1512 $search_columns_parts[ $search_column ] = $wpdb->prepare( "({$wpdb->posts}.$search_column $like_op %s)", $like );
1513 }
1514
1515 if ( ! empty( $this->allow_query_attachment_by_filename ) ) {
1516 $search_columns_parts['attachment'] = $wpdb->prepare( "(sq1.meta_value $like_op %s)", $like );
1517 }
1518
1519 $search .= "$searchand(" . implode( " $andor_op ", $search_columns_parts ) . ')';
1520
1521 $searchand = ' AND ';
1522 }
1523
1524 if ( ! empty( $search ) ) {
1525 $search = " AND ({$search}) ";
1526 if ( ! is_user_logged_in() ) {
1527 $search .= " AND ({$wpdb->posts}.post_password = '') ";
1528 }
1529 }
1530
1531 return $search;
1532 }
1533
1534 /**
1535 * Checks if the terms are suitable for searching.
1536 *
1537 * Uses an array of stopwords (terms) that are excluded from the separate
1538 * term matching when searching for posts. The list of English stopwords is
1539 * the approximate search engines list, and is translatable.
1540 *
1541 * @since 3.7.0
1542 *
1543 * @param string[] $terms Array of terms to check.
1544 * @return string[] Terms that are not stopwords.
1545 */
1546 protected function parse_search_terms( $terms ) {
1547 $strtolower = function_exists( 'mb_strtolower' ) ? 'mb_strtolower' : 'strtolower';
1548 $checked = array();
1549
1550 $stopwords = $this->get_search_stopwords();
1551
1552 foreach ( $terms as $term ) {
1553 // Keep before/after spaces when term is for exact match.
1554 if ( preg_match( '/^".+"$/', $term ) ) {
1555 $term = trim( $term, "\"'" );
1556 } else {
1557 $term = trim( $term, "\"' " );
1558 }
1559
1560 // Avoid single A-Z and single dashes.
1561 if ( ! $term || ( 1 === strlen( $term ) && preg_match( '/^[a-z\-]$/i', $term ) ) ) {
1562 continue;
1563 }
1564
1565 if ( in_array( call_user_func( $strtolower, $term ), $stopwords, true ) ) {
1566 continue;
1567 }
1568
1569 $checked[] = $term;
1570 }
1571
1572 return $checked;
1573 }
1574
1575 /**
1576 * Retrieves stopwords used when parsing search terms.
1577 *
1578 * @since 3.7.0
1579 *
1580 * @return string[] Stopwords.
1581 */
1582 protected function get_search_stopwords() {
1583 if ( isset( $this->stopwords ) ) {
1584 return $this->stopwords;
1585 }
1586
1587 /*
1588 * translators: This is a comma-separated list of very common words that should be excluded from a search,
1589 * like a, an, and the. These are usually called "stopwords". You should not simply translate these individual
1590 * words into your language. Instead, look for and provide commonly accepted stopwords in your language.
1591 */
1592 $words = explode(
1593 ',',
1594 _x(
1595 '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',
1596 'Comma-separated list of search stopwords in your language'
1597 )
1598 );
1599
1600 $stopwords = array();
1601 foreach ( $words as $word ) {
1602 $word = trim( $word, "\r\n\t " );
1603 if ( $word ) {
1604 $stopwords[] = $word;
1605 }
1606 }
1607
1608 /**
1609 * Filters stopwords used when parsing search terms.
1610 *
1611 * @since 3.7.0
1612 *
1613 * @param string[] $stopwords Array of stopwords.
1614 */
1615 $this->stopwords = apply_filters( 'wp_search_stopwords', $stopwords );
1616 return $this->stopwords;
1617 }
1618
1619 /**
1620 * Generates SQL for the ORDER BY condition based on passed search terms.
1621 *
1622 * @since 3.7.0
1623 *
1624 * @global wpdb $wpdb WordPress database abstraction object.
1625 *
1626 * @param array $query_vars Query variables.
1627 * @return string ORDER BY clause.
1628 */
1629 protected function parse_search_order( &$query_vars ) {
1630 global $wpdb;
1631
1632 if ( $query_vars['search_terms_count'] > 1 ) {
1633 $num_terms = count( $query_vars['search_orderby_title'] );
1634
1635 // If the search terms contain negative queries, don't bother ordering by sentence matches.
1636 $like = '';
1637 if ( ! preg_match( '/(?:\s|^)\-/', $query_vars['s'] ) ) {
1638 $like = '%' . $wpdb->esc_like( $query_vars['s'] ) . '%';
1639 }
1640
1641 $search_orderby = '';
1642
1643 // Sentence match in 'post_title'.
1644 if ( $like ) {
1645 $search_orderby .= $wpdb->prepare( "WHEN {$wpdb->posts}.post_title LIKE %s THEN 1 ", $like );
1646 }
1647
1648 /*
1649 * Sanity limit, sort as sentence when more than 6 terms
1650 * (few searches are longer than 6 terms and most titles are not).
1651 */
1652 if ( $num_terms < 7 ) {
1653 // All words in title.
1654 $search_orderby .= 'WHEN ' . implode( ' AND ', $query_vars['search_orderby_title'] ) . ' THEN 2 ';
1655 // Any word in title, not needed when $num_terms == 1.
1656 if ( $num_terms > 1 ) {
1657 $search_orderby .= 'WHEN ' . implode( ' OR ', $query_vars['search_orderby_title'] ) . ' THEN 3 ';
1658 }
1659 }
1660
1661 // Sentence match in 'post_content' and 'post_excerpt'.
1662 if ( $like ) {
1663 $search_orderby .= $wpdb->prepare( "WHEN {$wpdb->posts}.post_excerpt LIKE %s THEN 4 ", $like );
1664 $search_orderby .= $wpdb->prepare( "WHEN {$wpdb->posts}.post_content LIKE %s THEN 5 ", $like );
1665 }
1666
1667 if ( $search_orderby ) {
1668 $search_orderby = '(CASE ' . $search_orderby . 'ELSE 6 END)';
1669 }
1670 } else {
1671 // Single word or sentence search.
1672 $search_orderby = reset( $query_vars['search_orderby_title'] ) . ' DESC';
1673 }
1674
1675 return $search_orderby;
1676 }
1677
1678 /**
1679 * Converts the given orderby alias (if allowed) to a properly-prefixed value.
1680 *
1681 * @since 4.0.0
1682 *
1683 * @global wpdb $wpdb WordPress database abstraction object.
1684 *
1685 * @param string $orderby Alias for the field to order by.
1686 * @return string|false Table-prefixed value to used in the ORDER clause. False otherwise.
1687 */
1688 protected function parse_orderby( $orderby ) {
1689 global $wpdb;
1690
1691 // Used to filter values.
1692 $allowed_keys = array(
1693 'post_name',
1694 'post_author',
1695 'post_date',
1696 'post_title',
1697 'post_modified',
1698 'post_parent',
1699 'post_type',
1700 'name',
1701 'author',
1702 'date',
1703 'title',
1704 'modified',
1705 'parent',
1706 'type',
1707 'ID',
1708 'menu_order',
1709 'comment_count',
1710 'rand',
1711 'post__in',
1712 'post_parent__in',
1713 'post_name__in',
1714 );
1715
1716 $primary_meta_key = '';
1717 $primary_meta_query = false;
1718 $meta_clauses = $this->meta_query->get_clauses();
1719 if ( ! empty( $meta_clauses ) ) {
1720 $primary_meta_query = reset( $meta_clauses );
1721
1722 if ( ! empty( $primary_meta_query['key'] ) ) {
1723 $primary_meta_key = $primary_meta_query['key'];
1724 $allowed_keys[] = $primary_meta_key;
1725 }
1726
1727 $allowed_keys[] = 'meta_value';
1728 $allowed_keys[] = 'meta_value_num';
1729 $allowed_keys = array_merge( $allowed_keys, array_keys( $meta_clauses ) );
1730 }
1731
1732 // If RAND() contains a seed value, sanitize and add to allowed keys.
1733 $rand_with_seed = false;
1734 if ( preg_match( '/RAND\(([0-9]+)\)/i', $orderby, $matches ) ) {
1735 $orderby = sprintf( 'RAND(%s)', (int) $matches[1] );
1736 $allowed_keys[] = $orderby;
1737 $rand_with_seed = true;
1738 }
1739
1740 if ( ! in_array( $orderby, $allowed_keys, true ) ) {
1741 return false;
1742 }
1743
1744 $orderby_clause = '';
1745
1746 switch ( $orderby ) {
1747 case 'post_name':
1748 case 'post_author':
1749 case 'post_date':
1750 case 'post_title':
1751 case 'post_modified':
1752 case 'post_parent':
1753 case 'post_type':
1754 case 'ID':
1755 case 'menu_order':
1756 case 'comment_count':
1757 $orderby_clause = "{$wpdb->posts}.{$orderby}";
1758 break;
1759 case 'rand':
1760 $orderby_clause = 'RAND()';
1761 break;
1762 case $primary_meta_key:
1763 case 'meta_value':
1764 if ( ! empty( $primary_meta_query['type'] ) ) {
1765 $orderby_clause = "CAST({$primary_meta_query['alias']}.meta_value AS {$primary_meta_query['cast']})";
1766 } else {
1767 $orderby_clause = "{$primary_meta_query['alias']}.meta_value";
1768 }
1769 break;
1770 case 'meta_value_num':
1771 $orderby_clause = "{$primary_meta_query['alias']}.meta_value+0";
1772 break;
1773 case 'post__in':
1774 if ( ! empty( $this->query_vars['post__in'] ) ) {
1775 $orderby_clause = "FIELD({$wpdb->posts}.ID," . implode( ',', array_map( 'absint', $this->query_vars['post__in'] ) ) . ')';
1776 }
1777 break;
1778 case 'post_parent__in':
1779 if ( ! empty( $this->query_vars['post_parent__in'] ) ) {
1780 $orderby_clause = "FIELD( {$wpdb->posts}.post_parent," . implode( ', ', array_map( 'absint', $this->query_vars['post_parent__in'] ) ) . ' )';
1781 }
1782 break;
1783 case 'post_name__in':
1784 if ( ! empty( $this->query_vars['post_name__in'] ) ) {
1785 $post_name__in = array_map( 'sanitize_title_for_query', $this->query_vars['post_name__in'] );
1786 $post_name__in_string = "'" . implode( "','", $post_name__in ) . "'";
1787 $orderby_clause = "FIELD( {$wpdb->posts}.post_name," . $post_name__in_string . ' )';
1788 }
1789 break;
1790 default:
1791 if ( array_key_exists( $orderby, $meta_clauses ) ) {
1792 // $orderby corresponds to a meta_query clause.
1793 $meta_clause = $meta_clauses[ $orderby ];
1794 $orderby_clause = "CAST({$meta_clause['alias']}.meta_value AS {$meta_clause['cast']})";
1795 } elseif ( $rand_with_seed ) {
1796 $orderby_clause = $orderby;
1797 } else {
1798 // Default: order by post field.
1799 $orderby_clause = "{$wpdb->posts}.post_" . sanitize_key( $orderby );
1800 }
1801
1802 break;
1803 }
1804
1805 return $orderby_clause;
1806 }
1807
1808 /**
1809 * Parse an 'order' query variable and cast it to ASC or DESC as necessary.
1810 *
1811 * @since 4.0.0
1812 *
1813 * @param string $order The 'order' query variable.
1814 * @return string The sanitized 'order' query variable.
1815 */
1816 protected function parse_order( $order ) {
1817 if ( ! is_string( $order ) || empty( $order ) ) {
1818 return 'DESC';
1819 }
1820
1821 if ( 'ASC' === strtoupper( $order ) ) {
1822 return 'ASC';
1823 } else {
1824 return 'DESC';
1825 }
1826 }
1827
1828 /**
1829 * Sets the 404 property and saves whether query is feed.
1830 *
1831 * @since 2.0.0
1832 */
1833 public function set_404() {
1834 $is_feed = $this->is_feed;
1835
1836 $this->init_query_flags();
1837 $this->is_404 = true;
1838
1839 $this->is_feed = $is_feed;
1840
1841 /**
1842 * Fires after a 404 is triggered.
1843 *
1844 * @since 5.5.0
1845 *
1846 * @param WP_Query $query The WP_Query instance (passed by reference).
1847 */
1848 do_action_ref_array( 'set_404', array( $this ) );
1849 }
1850
1851 /**
1852 * Retrieves the value of a query variable.
1853 *
1854 * @since 1.5.0
1855 * @since 3.9.0 The `$default_value` argument was introduced.
1856 *
1857 * @param string $query_var Query variable key.
1858 * @param mixed $default_value Optional. Value to return if the query variable is not set.
1859 * Default empty string.
1860 * @return mixed Contents of the query variable.
1861 */
1862 public function get( $query_var, $default_value = '' ) {
1863 if ( isset( $this->query_vars[ $query_var ] ) ) {
1864 return $this->query_vars[ $query_var ];
1865 }
1866
1867 return $default_value;
1868 }
1869
1870 /**
1871 * Sets the value of a query variable.
1872 *
1873 * @since 1.5.0
1874 *
1875 * @param string $query_var Query variable key.
1876 * @param mixed $value Query variable value.
1877 */
1878 public function set( $query_var, $value ) {
1879 $this->query_vars[ $query_var ] = $value;
1880 }
1881
1882 /**
1883 * Retrieves an array of posts based on query variables.
1884 *
1885 * There are a few filters and actions that can be used to modify the post
1886 * database query.
1887 *
1888 * @since 1.5.0
1889 *
1890 * @global wpdb $wpdb WordPress database abstraction object.
1891 *
1892 * @return WP_Post[]|int[] Array of post objects or post IDs.
1893 */
1894 public function get_posts() {
1895 global $wpdb;
1896
1897 $this->parse_query();
1898
1899 /**
1900 * Fires after the query variable object is created, but before the actual query is run.
1901 *
1902 * Note: If using conditional tags, use the method versions within the passed instance
1903 * (e.g. $this->is_main_query() instead of is_main_query()). This is because the functions
1904 * like is_main_query() test against the global $wp_query instance, not the passed one.
1905 *
1906 * @since 2.0.0
1907 *
1908 * @param WP_Query $query The WP_Query instance (passed by reference).
1909 */
1910 do_action_ref_array( 'pre_get_posts', array( &$this ) );
1911
1912 // Locally scoped reference for easy of use.
1913 $query_vars = &$this->query_vars;
1914
1915 // Fill again in case 'pre_get_posts' unset some vars.
1916 $query_vars = $this->fill_query_vars( $query_vars );
1917
1918 /**
1919 * Filters whether an attachment query should include filenames or not.
1920 *
1921 * @since 6.0.3
1922 *
1923 * @param bool $allow_query_attachment_by_filename Whether or not to include filenames.
1924 */
1925 $this->allow_query_attachment_by_filename = apply_filters( 'wp_allow_query_attachment_by_filename', false );
1926 remove_all_filters( 'wp_allow_query_attachment_by_filename' );
1927
1928 // Parse meta query.
1929 $this->meta_query = new WP_Meta_Query();
1930 $this->meta_query->parse_query_vars( $query_vars );
1931
1932 // Set a flag if a 'pre_get_posts' hook changed the query vars.
1933 $hash = md5( serialize( $this->query_vars ) );
1934 if ( $hash !== $this->query_vars_hash ) {
1935 $this->query_vars_changed = true;
1936 $this->query_vars_hash = $hash;
1937 }
1938 unset( $hash );
1939
1940 // First let's clear some variables.
1941 $distinct = '';
1942 $whichauthor = '';
1943 $whichmimetype = '';
1944 $where = '';
1945 $limits = '';
1946 $join = '';
1947 $search = '';
1948 $groupby = '';
1949 $post_status_join = false;
1950 $page = 1;
1951
1952 if ( isset( $query_vars['caller_get_posts'] ) ) {
1953 _deprecated_argument(
1954 'WP_Query',
1955 '3.1.0',
1956 sprintf(
1957 /* translators: 1: caller_get_posts, 2: ignore_sticky_posts */
1958 __( '%1$s is deprecated. Use %2$s instead.' ),
1959 '<code>caller_get_posts</code>',
1960 '<code>ignore_sticky_posts</code>'
1961 )
1962 );
1963
1964 if ( ! isset( $query_vars['ignore_sticky_posts'] ) ) {
1965 $query_vars['ignore_sticky_posts'] = $query_vars['caller_get_posts'];
1966 }
1967 }
1968
1969 if ( ! isset( $query_vars['ignore_sticky_posts'] ) ) {
1970 $query_vars['ignore_sticky_posts'] = false;
1971 }
1972
1973 if ( ! isset( $query_vars['suppress_filters'] ) ) {
1974 $query_vars['suppress_filters'] = false;
1975 }
1976
1977 if ( ! isset( $query_vars['cache_results'] ) ) {
1978 $query_vars['cache_results'] = true;
1979 }
1980
1981 if ( ! isset( $query_vars['update_post_term_cache'] ) ) {
1982 $query_vars['update_post_term_cache'] = true;
1983 }
1984
1985 if ( ! isset( $query_vars['update_menu_item_cache'] ) ) {
1986 $query_vars['update_menu_item_cache'] = false;
1987 }
1988
1989 if ( ! isset( $query_vars['lazy_load_term_meta'] ) ) {
1990 $query_vars['lazy_load_term_meta'] = $query_vars['update_post_term_cache'];
1991 } elseif ( $query_vars['lazy_load_term_meta'] ) { // Lazy loading term meta only works if term caches are primed.
1992 $query_vars['update_post_term_cache'] = true;
1993 }
1994
1995 if ( ! isset( $query_vars['update_post_meta_cache'] ) ) {
1996 $query_vars['update_post_meta_cache'] = true;
1997 }
1998
1999 if ( ! isset( $query_vars['post_type'] ) ) {
2000 if ( $this->is_search ) {
2001 $query_vars['post_type'] = 'any';
2002 } else {
2003 $query_vars['post_type'] = '';
2004 }
2005 }
2006 $post_type = $query_vars['post_type'];
2007 if ( empty( $query_vars['posts_per_page'] ) ) {
2008 $query_vars['posts_per_page'] = get_option( 'posts_per_page' );
2009 }
2010 if ( isset( $query_vars['showposts'] ) && $query_vars['showposts'] ) {
2011 $query_vars['showposts'] = (int) $query_vars['showposts'];
2012 $query_vars['posts_per_page'] = $query_vars['showposts'];
2013 }
2014 if ( ( isset( $query_vars['posts_per_archive_page'] ) && 0 != $query_vars['posts_per_archive_page'] ) && ( $this->is_archive || $this->is_search ) ) {
2015 $query_vars['posts_per_page'] = $query_vars['posts_per_archive_page'];
2016 }
2017 if ( ! isset( $query_vars['nopaging'] ) ) {
2018 if ( -1 == $query_vars['posts_per_page'] ) {
2019 $query_vars['nopaging'] = true;
2020 } else {
2021 $query_vars['nopaging'] = false;
2022 }
2023 }
2024
2025 if ( $this->is_feed ) {
2026 // This overrides 'posts_per_page'.
2027 if ( ! empty( $query_vars['posts_per_rss'] ) ) {
2028 $query_vars['posts_per_page'] = $query_vars['posts_per_rss'];
2029 } else {
2030 $query_vars['posts_per_page'] = get_option( 'posts_per_rss' );
2031 }
2032 $query_vars['nopaging'] = false;
2033 }
2034
2035 $query_vars['posts_per_page'] = (int) $query_vars['posts_per_page'];
2036 if ( $query_vars['posts_per_page'] < -1 ) {
2037 $query_vars['posts_per_page'] = abs( $query_vars['posts_per_page'] );
2038 } elseif ( 0 === $query_vars['posts_per_page'] ) {
2039 $query_vars['posts_per_page'] = 1;
2040 }
2041
2042 if ( ! isset( $query_vars['comments_per_page'] ) || 0 == $query_vars['comments_per_page'] ) {
2043 $query_vars['comments_per_page'] = get_option( 'comments_per_page' );
2044 }
2045
2046 if ( $this->is_home && ( empty( $this->query ) || 'true' === $query_vars['preview'] ) && ( 'page' === get_option( 'show_on_front' ) ) && get_option( 'page_on_front' ) ) {
2047 $this->is_page = true;
2048 $this->is_home = false;
2049 $query_vars['page_id'] = get_option( 'page_on_front' );
2050 }
2051
2052 if ( isset( $query_vars['page'] ) ) {
2053 $query_vars['page'] = is_scalar( $query_vars['page'] ) ? absint( trim( $query_vars['page'], '/' ) ) : 0;
2054 }
2055
2056 // If true, forcibly turns off SQL_CALC_FOUND_ROWS even when limits are present.
2057 if ( isset( $query_vars['no_found_rows'] ) ) {
2058 $query_vars['no_found_rows'] = (bool) $query_vars['no_found_rows'];
2059 } else {
2060 $query_vars['no_found_rows'] = false;
2061 }
2062
2063 switch ( $query_vars['fields'] ) {
2064 case 'ids':
2065 $fields = "{$wpdb->posts}.ID";
2066 break;
2067 case 'id=>parent':
2068 $fields = "{$wpdb->posts}.ID, {$wpdb->posts}.post_parent";
2069 break;
2070 case '':
2071 /*
2072 * Set the default to 'all'.
2073 *
2074 * This is used in `WP_Query::the_post` to determine if the
2075 * entire post object has been queried.
2076 */
2077 $query_vars['fields'] = 'all';
2078 // Falls through.
2079 default:
2080 $fields = "{$wpdb->posts}.*";
2081 }
2082
2083 if ( '' !== $query_vars['menu_order'] ) {
2084 $where .= " AND {$wpdb->posts}.menu_order = " . $query_vars['menu_order'];
2085 }
2086 // The "m" parameter is meant for months but accepts datetimes of varying specificity.
2087 if ( $query_vars['m'] ) {
2088 $where .= " AND YEAR({$wpdb->posts}.post_date)=" . substr( $query_vars['m'], 0, 4 );
2089 if ( strlen( $query_vars['m'] ) > 5 ) {
2090 $where .= " AND MONTH({$wpdb->posts}.post_date)=" . substr( $query_vars['m'], 4, 2 );
2091 }
2092 if ( strlen( $query_vars['m'] ) > 7 ) {
2093 $where .= " AND DAYOFMONTH({$wpdb->posts}.post_date)=" . substr( $query_vars['m'], 6, 2 );
2094 }
2095 if ( strlen( $query_vars['m'] ) > 9 ) {
2096 $where .= " AND HOUR({$wpdb->posts}.post_date)=" . substr( $query_vars['m'], 8, 2 );
2097 }
2098 if ( strlen( $query_vars['m'] ) > 11 ) {
2099 $where .= " AND MINUTE({$wpdb->posts}.post_date)=" . substr( $query_vars['m'], 10, 2 );
2100 }
2101 if ( strlen( $query_vars['m'] ) > 13 ) {
2102 $where .= " AND SECOND({$wpdb->posts}.post_date)=" . substr( $query_vars['m'], 12, 2 );
2103 }
2104 }
2105
2106 // Handle the other individual date parameters.
2107 $date_parameters = array();
2108
2109 if ( '' !== $query_vars['hour'] ) {
2110 $date_parameters['hour'] = $query_vars['hour'];
2111 }
2112
2113 if ( '' !== $query_vars['minute'] ) {
2114 $date_parameters['minute'] = $query_vars['minute'];
2115 }
2116
2117 if ( '' !== $query_vars['second'] ) {
2118 $date_parameters['second'] = $query_vars['second'];
2119 }
2120
2121 if ( $query_vars['year'] ) {
2122 $date_parameters['year'] = $query_vars['year'];
2123 }
2124
2125 if ( $query_vars['monthnum'] ) {
2126 $date_parameters['monthnum'] = $query_vars['monthnum'];
2127 }
2128
2129 if ( $query_vars['w'] ) {
2130 $date_parameters['week'] = $query_vars['w'];
2131 }
2132
2133 if ( $query_vars['day'] ) {
2134 $date_parameters['day'] = $query_vars['day'];
2135 }
2136
2137 if ( $date_parameters ) {
2138 $date_query = new WP_Date_Query( array( $date_parameters ) );
2139 $where .= $date_query->get_sql();
2140 }
2141 unset( $date_parameters, $date_query );
2142
2143 // Handle complex date queries.
2144 if ( ! empty( $query_vars['date_query'] ) ) {
2145 $this->date_query = new WP_Date_Query( $query_vars['date_query'] );
2146 $where .= $this->date_query->get_sql();
2147 }
2148
2149 // If we've got a post_type AND it's not "any" post_type.
2150 if ( ! empty( $query_vars['post_type'] ) && 'any' !== $query_vars['post_type'] ) {
2151 foreach ( (array) $query_vars['post_type'] as $_post_type ) {
2152 $ptype_obj = get_post_type_object( $_post_type );
2153 if ( ! $ptype_obj || ! $ptype_obj->query_var || empty( $query_vars[ $ptype_obj->query_var ] ) ) {
2154 continue;
2155 }
2156
2157 if ( ! $ptype_obj->hierarchical ) {
2158 // Non-hierarchical post types can directly use 'name'.
2159 $query_vars['name'] = $query_vars[ $ptype_obj->query_var ];
2160 } else {
2161 // Hierarchical post types will operate through 'pagename'.
2162 $query_vars['pagename'] = $query_vars[ $ptype_obj->query_var ];
2163 $query_vars['name'] = '';
2164 }
2165
2166 // Only one request for a slug is possible, this is why name & pagename are overwritten above.
2167 break;
2168 } // End foreach.
2169 unset( $ptype_obj );
2170 }
2171
2172 if ( '' !== $query_vars['title'] ) {
2173 $where .= $wpdb->prepare( " AND {$wpdb->posts}.post_title = %s", stripslashes( $query_vars['title'] ) );
2174 }
2175
2176 // Parameters related to 'post_name'.
2177 if ( '' !== $query_vars['name'] ) {
2178 $query_vars['name'] = sanitize_title_for_query( $query_vars['name'] );
2179 $where .= " AND {$wpdb->posts}.post_name = '" . $query_vars['name'] . "'";
2180 } elseif ( '' !== $query_vars['pagename'] ) {
2181 if ( isset( $this->queried_object_id ) ) {
2182 $reqpage = $this->queried_object_id;
2183 } else {
2184 if ( 'page' !== $query_vars['post_type'] ) {
2185 foreach ( (array) $query_vars['post_type'] as $_post_type ) {
2186 $ptype_obj = get_post_type_object( $_post_type );
2187 if ( ! $ptype_obj || ! $ptype_obj->hierarchical ) {
2188 continue;
2189 }
2190
2191 $reqpage = get_page_by_path( $query_vars['pagename'], OBJECT, $_post_type );
2192 if ( $reqpage ) {
2193 break;
2194 }
2195 }
2196 unset( $ptype_obj );
2197 } else {
2198 $reqpage = get_page_by_path( $query_vars['pagename'] );
2199 }
2200 if ( ! empty( $reqpage ) ) {
2201 $reqpage = $reqpage->ID;
2202 } else {
2203 $reqpage = 0;
2204 }
2205 }
2206
2207 $page_for_posts = get_option( 'page_for_posts' );
2208 if ( ( 'page' !== get_option( 'show_on_front' ) ) || empty( $page_for_posts ) || ( $reqpage != $page_for_posts ) ) {
2209 $query_vars['pagename'] = sanitize_title_for_query( wp_basename( $query_vars['pagename'] ) );
2210 $query_vars['name'] = $query_vars['pagename'];
2211 $where .= " AND ({$wpdb->posts}.ID = '$reqpage')";
2212 $reqpage_obj = get_post( $reqpage );
2213 if ( is_object( $reqpage_obj ) && 'attachment' === $reqpage_obj->post_type ) {
2214 $this->is_attachment = true;
2215 $post_type = 'attachment';
2216 $query_vars['post_type'] = 'attachment';
2217 $this->is_page = true;
2218 $query_vars['attachment_id'] = $reqpage;
2219 }
2220 }
2221 } elseif ( '' !== $query_vars['attachment'] ) {
2222 $query_vars['attachment'] = sanitize_title_for_query( wp_basename( $query_vars['attachment'] ) );
2223 $query_vars['name'] = $query_vars['attachment'];
2224 $where .= " AND {$wpdb->posts}.post_name = '" . $query_vars['attachment'] . "'";
2225 } elseif ( is_array( $query_vars['post_name__in'] ) && ! empty( $query_vars['post_name__in'] ) ) {
2226 $query_vars['post_name__in'] = array_map( 'sanitize_title_for_query', $query_vars['post_name__in'] );
2227 // Duplicate array before sorting to allow for the orderby clause.
2228 $post_name__in_for_where = array_unique( $query_vars['post_name__in'] );
2229 sort( $post_name__in_for_where );
2230 $post_name__in = "'" . implode( "','", $post_name__in_for_where ) . "'";
2231 $where .= " AND {$wpdb->posts}.post_name IN ($post_name__in)";
2232 }
2233
2234 // If an attachment is requested by number, let it supersede any post number.
2235 if ( $query_vars['attachment_id'] ) {
2236 $query_vars['p'] = absint( $query_vars['attachment_id'] );
2237 }
2238
2239 // If a post number is specified, load that post.
2240 if ( $query_vars['p'] ) {
2241 $where .= " AND {$wpdb->posts}.ID = " . $query_vars['p'];
2242 } elseif ( $query_vars['post__in'] ) {
2243 // Duplicate array before sorting to allow for the orderby clause.
2244 $post__in_for_where = $query_vars['post__in'];
2245 $post__in_for_where = array_unique( array_map( 'absint', $post__in_for_where ) );
2246 sort( $post__in_for_where );
2247 $post__in = implode( ',', array_map( 'absint', $post__in_for_where ) );
2248 $where .= " AND {$wpdb->posts}.ID IN ($post__in)";
2249 } elseif ( $query_vars['post__not_in'] ) {
2250 sort( $query_vars['post__not_in'] );
2251 $post__not_in = implode( ',', array_map( 'absint', $query_vars['post__not_in'] ) );
2252 $where .= " AND {$wpdb->posts}.ID NOT IN ($post__not_in)";
2253 }
2254
2255 if ( is_numeric( $query_vars['post_parent'] ) ) {
2256 $where .= $wpdb->prepare( " AND {$wpdb->posts}.post_parent = %d ", $query_vars['post_parent'] );
2257 } elseif ( $query_vars['post_parent__in'] ) {
2258 // Duplicate array before sorting to allow for the orderby clause.
2259 $post_parent__in_for_where = $query_vars['post_parent__in'];
2260 $post_parent__in_for_where = array_unique( array_map( 'absint', $post_parent__in_for_where ) );
2261 sort( $post_parent__in_for_where );
2262 $post_parent__in = implode( ',', array_map( 'absint', $post_parent__in_for_where ) );
2263 $where .= " AND {$wpdb->posts}.post_parent IN ($post_parent__in)";
2264 } elseif ( $query_vars['post_parent__not_in'] ) {
2265 sort( $query_vars['post_parent__not_in'] );
2266 $post_parent__not_in = implode( ',', array_map( 'absint', $query_vars['post_parent__not_in'] ) );
2267 $where .= " AND {$wpdb->posts}.post_parent NOT IN ($post_parent__not_in)";
2268 }
2269
2270 if ( $query_vars['page_id'] ) {
2271 if ( ( 'page' !== get_option( 'show_on_front' ) ) || ( get_option( 'page_for_posts' ) != $query_vars['page_id'] ) ) {
2272 $query_vars['p'] = $query_vars['page_id'];
2273 $where = " AND {$wpdb->posts}.ID = " . $query_vars['page_id'];
2274 }
2275 }
2276
2277 // If a search pattern is specified, load the posts that match.
2278 if ( strlen( $query_vars['s'] ) ) {
2279 $search = $this->parse_search( $query_vars );
2280 }
2281
2282 if ( ! $query_vars['suppress_filters'] ) {
2283 /**
2284 * Filters the search SQL that is used in the WHERE clause of WP_Query.
2285 *
2286 * @since 3.0.0
2287 *
2288 * @param string $search Search SQL for WHERE clause.
2289 * @param WP_Query $query The current WP_Query object.
2290 */
2291 $search = apply_filters_ref_array( 'posts_search', array( $search, &$this ) );
2292 }
2293
2294 // Taxonomies.
2295 if ( ! $this->is_singular ) {
2296 $this->parse_tax_query( $query_vars );
2297
2298 $clauses = $this->tax_query->get_sql( $wpdb->posts, 'ID' );
2299
2300 $join .= $clauses['join'];
2301 $where .= $clauses['where'];
2302 }
2303
2304 if ( $this->is_tax ) {
2305 if ( empty( $post_type ) ) {
2306 // Do a fully inclusive search for currently registered post types of queried taxonomies.
2307 $post_type = array();
2308 $taxonomies = array_keys( $this->tax_query->queried_terms );
2309 foreach ( get_post_types( array( 'exclude_from_search' => false ) ) as $pt ) {
2310 $object_taxonomies = 'attachment' === $pt ? get_taxonomies_for_attachments() : get_object_taxonomies( $pt );
2311 if ( array_intersect( $taxonomies, $object_taxonomies ) ) {
2312 $post_type[] = $pt;
2313 }
2314 }
2315 if ( ! $post_type ) {
2316 $post_type = 'any';
2317 } elseif ( count( $post_type ) === 1 ) {
2318 $post_type = $post_type[0];
2319 } else {
2320 // Sort post types to ensure same cache key generation.
2321 sort( $post_type );
2322 }
2323
2324 $post_status_join = true;
2325 } elseif ( in_array( 'attachment', (array) $post_type, true ) ) {
2326 $post_status_join = true;
2327 }
2328 }
2329
2330 /*
2331 * Ensure that 'taxonomy', 'term', 'term_id', 'cat', and
2332 * 'category_name' vars are set for backward compatibility.
2333 */
2334 if ( ! empty( $this->tax_query->queried_terms ) ) {
2335
2336 /*
2337 * Set 'taxonomy', 'term', and 'term_id' to the
2338 * first taxonomy other than 'post_tag' or 'category'.
2339 */
2340 if ( ! isset( $query_vars['taxonomy'] ) ) {
2341 foreach ( $this->tax_query->queried_terms as $queried_taxonomy => $queried_items ) {
2342 if ( empty( $queried_items['terms'][0] ) ) {
2343 continue;
2344 }
2345
2346 if ( ! in_array( $queried_taxonomy, array( 'category', 'post_tag' ), true ) ) {
2347 $query_vars['taxonomy'] = $queried_taxonomy;
2348
2349 if ( 'slug' === $queried_items['field'] ) {
2350 $query_vars['term'] = $queried_items['terms'][0];
2351 } else {
2352 $query_vars['term_id'] = $queried_items['terms'][0];
2353 }
2354
2355 // Take the first one we find.
2356 break;
2357 }
2358 }
2359 }
2360
2361 // 'cat', 'category_name', 'tag_id'.
2362 foreach ( $this->tax_query->queried_terms as $queried_taxonomy => $queried_items ) {
2363 if ( empty( $queried_items['terms'][0] ) ) {
2364 continue;
2365 }
2366
2367 if ( 'category' === $queried_taxonomy ) {
2368 $the_cat = get_term_by( $queried_items['field'], $queried_items['terms'][0], 'category' );
2369 if ( $the_cat ) {
2370 $this->set( 'cat', $the_cat->term_id );
2371 $this->set( 'category_name', $the_cat->slug );
2372 }
2373 unset( $the_cat );
2374 }
2375
2376 if ( 'post_tag' === $queried_taxonomy ) {
2377 $the_tag = get_term_by( $queried_items['field'], $queried_items['terms'][0], 'post_tag' );
2378 if ( $the_tag ) {
2379 $this->set( 'tag_id', $the_tag->term_id );
2380 }
2381 unset( $the_tag );
2382 }
2383 }
2384 }
2385
2386 if ( ! empty( $this->tax_query->queries ) || ! empty( $this->meta_query->queries ) || ! empty( $this->allow_query_attachment_by_filename ) ) {
2387 $groupby = "{$wpdb->posts}.ID";
2388 }
2389
2390 // Author/user stuff.
2391
2392 if ( ! empty( $query_vars['author'] ) && '0' != $query_vars['author'] ) {
2393 $query_vars['author'] = addslashes_gpc( '' . urldecode( $query_vars['author'] ) );
2394 $authors = array_unique( array_map( 'intval', preg_split( '/[,\s]+/', $query_vars['author'] ) ) );
2395 sort( $authors );
2396 foreach ( $authors as $author ) {
2397 $key = $author > 0 ? 'author__in' : 'author__not_in';
2398 $query_vars[ $key ][] = abs( $author );
2399 }
2400 $query_vars['author'] = implode( ',', $authors );
2401 }
2402
2403 if ( ! empty( $query_vars['author__not_in'] ) ) {
2404 if ( is_array( $query_vars['author__not_in'] ) ) {
2405 $query_vars['author__not_in'] = array_unique( array_map( 'absint', $query_vars['author__not_in'] ) );
2406 sort( $query_vars['author__not_in'] );
2407 }
2408 $author__not_in = implode( ',', (array) $query_vars['author__not_in'] );
2409 $where .= " AND {$wpdb->posts}.post_author NOT IN ($author__not_in) ";
2410 } elseif ( ! empty( $query_vars['author__in'] ) ) {
2411 if ( is_array( $query_vars['author__in'] ) ) {
2412 $query_vars['author__in'] = array_unique( array_map( 'absint', $query_vars['author__in'] ) );
2413 sort( $query_vars['author__in'] );
2414 }
2415 $author__in = implode( ',', array_map( 'absint', array_unique( (array) $query_vars['author__in'] ) ) );
2416 $where .= " AND {$wpdb->posts}.post_author IN ($author__in) ";
2417 }
2418
2419 // Author stuff for nice URLs.
2420
2421 if ( '' !== $query_vars['author_name'] ) {
2422 if ( str_contains( $query_vars['author_name'], '/' ) ) {
2423 $query_vars['author_name'] = explode( '/', $query_vars['author_name'] );
2424 if ( $query_vars['author_name'][ count( $query_vars['author_name'] ) - 1 ] ) {
2425 $query_vars['author_name'] = $query_vars['author_name'][ count( $query_vars['author_name'] ) - 1 ]; // No trailing slash.
2426 } else {
2427 $query_vars['author_name'] = $query_vars['author_name'][ count( $query_vars['author_name'] ) - 2 ]; // There was a trailing slash.
2428 }
2429 }
2430 $query_vars['author_name'] = sanitize_title_for_query( $query_vars['author_name'] );
2431 $query_vars['author'] = get_user_by( 'slug', $query_vars['author_name'] );
2432 if ( $query_vars['author'] ) {
2433 $query_vars['author'] = $query_vars['author']->ID;
2434 }
2435 $whichauthor .= " AND ({$wpdb->posts}.post_author = " . absint( $query_vars['author'] ) . ')';
2436 }
2437
2438 // Matching by comment count.
2439 if ( isset( $query_vars['comment_count'] ) ) {
2440 // Numeric comment count is converted to array format.
2441 if ( is_numeric( $query_vars['comment_count'] ) ) {
2442 $query_vars['comment_count'] = array(
2443 'value' => (int) $query_vars['comment_count'],
2444 );
2445 }
2446
2447 if ( isset( $query_vars['comment_count']['value'] ) ) {
2448 $query_vars['comment_count'] = array_merge(
2449 array(
2450 'compare' => '=',
2451 ),
2452 $query_vars['comment_count']
2453 );
2454
2455 // Fallback for invalid compare operators is '='.
2456 $compare_operators = array( '=', '!=', '>', '>=', '<', '<=' );
2457 if ( ! in_array( $query_vars['comment_count']['compare'], $compare_operators, true ) ) {
2458 $query_vars['comment_count']['compare'] = '=';
2459 }
2460
2461 $where .= $wpdb->prepare( " AND {$wpdb->posts}.comment_count {$query_vars['comment_count']['compare']} %d", $query_vars['comment_count']['value'] );
2462 }
2463 }
2464
2465 // MIME-Type stuff for attachment browsing.
2466
2467 if ( isset( $query_vars['post_mime_type'] ) && '' !== $query_vars['post_mime_type'] ) {
2468 $whichmimetype = wp_post_mime_type_where( $query_vars['post_mime_type'], $wpdb->posts );
2469 }
2470 $where .= $search . $whichauthor . $whichmimetype;
2471
2472 if ( ! empty( $this->allow_query_attachment_by_filename ) ) {
2473 $join .= " LEFT JOIN {$wpdb->postmeta} AS sq1 ON ( {$wpdb->posts}.ID = sq1.post_id AND sq1.meta_key = '_wp_attached_file' )";
2474 }
2475
2476 if ( ! empty( $this->meta_query->queries ) ) {
2477 $clauses = $this->meta_query->get_sql( 'post', $wpdb->posts, 'ID', $this );
2478 $join .= $clauses['join'];
2479 $where .= $clauses['where'];
2480 }
2481
2482 $rand = ( isset( $query_vars['orderby'] ) && 'rand' === $query_vars['orderby'] );
2483 if ( ! isset( $query_vars['order'] ) ) {
2484 $query_vars['order'] = $rand ? '' : 'DESC';
2485 } else {
2486 $query_vars['order'] = $rand ? '' : $this->parse_order( $query_vars['order'] );
2487 }
2488
2489 // These values of orderby should ignore the 'order' parameter.
2490 $force_asc = array( 'post__in', 'post_name__in', 'post_parent__in' );
2491 if ( isset( $query_vars['orderby'] ) && in_array( $query_vars['orderby'], $force_asc, true ) ) {
2492 $query_vars['order'] = '';
2493 }
2494
2495 // Order by.
2496 if ( empty( $query_vars['orderby'] ) ) {
2497 /*
2498 * Boolean false or empty array blanks out ORDER BY,
2499 * while leaving the value unset or otherwise empty sets the default.
2500 */
2501 if ( isset( $query_vars['orderby'] ) && ( is_array( $query_vars['orderby'] ) || false === $query_vars['orderby'] ) ) {
2502 $orderby = '';
2503 } else {
2504 $orderby = "{$wpdb->posts}.post_date " . $query_vars['order'];
2505 }
2506 } elseif ( 'none' === $query_vars['orderby'] ) {
2507 $orderby = '';
2508 } else {
2509 $orderby_array = array();
2510 if ( is_array( $query_vars['orderby'] ) ) {
2511 foreach ( $query_vars['orderby'] as $_orderby => $order ) {
2512 $orderby = addslashes_gpc( urldecode( $_orderby ) );
2513 $parsed = $this->parse_orderby( $orderby );
2514
2515 if ( ! $parsed ) {
2516 continue;
2517 }
2518
2519 $orderby_array[] = $parsed . ' ' . $this->parse_order( $order );
2520 }
2521 $orderby = implode( ', ', $orderby_array );
2522
2523 } else {
2524 $query_vars['orderby'] = urldecode( $query_vars['orderby'] );
2525 $query_vars['orderby'] = addslashes_gpc( $query_vars['orderby'] );
2526
2527 foreach ( explode( ' ', $query_vars['orderby'] ) as $i => $orderby ) {
2528 $parsed = $this->parse_orderby( $orderby );
2529 // Only allow certain values for safety.
2530 if ( ! $parsed ) {
2531 continue;
2532 }
2533
2534 $orderby_array[] = $parsed;
2535 }
2536 $orderby = implode( ' ' . $query_vars['order'] . ', ', $orderby_array );
2537
2538 if ( empty( $orderby ) ) {
2539 $orderby = "{$wpdb->posts}.post_date " . $query_vars['order'];
2540 } elseif ( ! empty( $query_vars['order'] ) ) {
2541 $orderby .= " {$query_vars['order']}";
2542 }
2543 }
2544 }
2545
2546 // Order search results by relevance only when another "orderby" is not specified in the query.
2547 if ( ! empty( $query_vars['s'] ) ) {
2548 $search_orderby = '';
2549 if ( ! empty( $query_vars['search_orderby_title'] ) && ( empty( $query_vars['orderby'] ) && ! $this->is_feed ) || ( isset( $query_vars['orderby'] ) && 'relevance' === $query_vars['orderby'] ) ) {
2550 $search_orderby = $this->parse_search_order( $query_vars );
2551 }
2552
2553 if ( ! $query_vars['suppress_filters'] ) {
2554 /**
2555 * Filters the ORDER BY used when ordering search results.
2556 *
2557 * @since 3.7.0
2558 *
2559 * @param string $search_orderby The ORDER BY clause.
2560 * @param WP_Query $query The current WP_Query instance.
2561 */
2562 $search_orderby = apply_filters( 'posts_search_orderby', $search_orderby, $this );
2563 }
2564
2565 if ( $search_orderby ) {
2566 $orderby = $orderby ? $search_orderby . ', ' . $orderby : $search_orderby;
2567 }
2568 }
2569
2570 if ( is_array( $post_type ) && count( $post_type ) > 1 ) {
2571 $post_type_cap = 'multiple_post_type';
2572 } else {
2573 if ( is_array( $post_type ) ) {
2574 $post_type = reset( $post_type );
2575 }
2576 $post_type_object = get_post_type_object( $post_type );
2577 if ( empty( $post_type_object ) ) {
2578 $post_type_cap = $post_type;
2579 }
2580 }
2581
2582 if ( isset( $query_vars['post_password'] ) ) {
2583 $where .= $wpdb->prepare( " AND {$wpdb->posts}.post_password = %s", $query_vars['post_password'] );
2584 if ( empty( $query_vars['perm'] ) ) {
2585 $query_vars['perm'] = 'readable';
2586 }
2587 } elseif ( isset( $query_vars['has_password'] ) ) {
2588 $where .= sprintf( " AND {$wpdb->posts}.post_password %s ''", $query_vars['has_password'] ? '!=' : '=' );
2589 }
2590
2591 if ( ! empty( $query_vars['comment_status'] ) ) {
2592 $where .= $wpdb->prepare( " AND {$wpdb->posts}.comment_status = %s ", $query_vars['comment_status'] );
2593 }
2594
2595 if ( ! empty( $query_vars['ping_status'] ) ) {
2596 $where .= $wpdb->prepare( " AND {$wpdb->posts}.ping_status = %s ", $query_vars['ping_status'] );
2597 }
2598
2599 $skip_post_status = false;
2600 if ( 'any' === $post_type ) {
2601 $in_search_post_types = get_post_types( array( 'exclude_from_search' => false ) );
2602 if ( empty( $in_search_post_types ) ) {
2603 $post_type_where = ' AND 1=0 ';
2604 $skip_post_status = true;
2605 } else {
2606 $post_type_where = " AND {$wpdb->posts}.post_type IN ('" . implode( "', '", array_map( 'esc_sql', $in_search_post_types ) ) . "')";
2607 }
2608 } elseif ( ! empty( $post_type ) && is_array( $post_type ) ) {
2609 // Sort post types to ensure same cache key generation.
2610 sort( $post_type );
2611 $post_type_where = " AND {$wpdb->posts}.post_type IN ('" . implode( "', '", esc_sql( $post_type ) ) . "')";
2612 } elseif ( ! empty( $post_type ) ) {
2613 $post_type_where = $wpdb->prepare( " AND {$wpdb->posts}.post_type = %s", $post_type );
2614 $post_type_object = get_post_type_object( $post_type );
2615 } elseif ( $this->is_attachment ) {
2616 $post_type_where = " AND {$wpdb->posts}.post_type = 'attachment'";
2617 $post_type_object = get_post_type_object( 'attachment' );
2618 } elseif ( $this->is_page ) {
2619 $post_type_where = " AND {$wpdb->posts}.post_type = 'page'";
2620 $post_type_object = get_post_type_object( 'page' );
2621 } else {
2622 $post_type_where = " AND {$wpdb->posts}.post_type = 'post'";
2623 $post_type_object = get_post_type_object( 'post' );
2624 }
2625
2626 $edit_cap = 'edit_post';
2627 $read_cap = 'read_post';
2628
2629 if ( ! empty( $post_type_object ) ) {
2630 $edit_others_cap = $post_type_object->cap->edit_others_posts;
2631 $read_private_cap = $post_type_object->cap->read_private_posts;
2632 } else {
2633 $edit_others_cap = 'edit_others_' . $post_type_cap . 's';
2634 $read_private_cap = 'read_private_' . $post_type_cap . 's';
2635 }
2636
2637 $user_id = get_current_user_id();
2638
2639 $q_status = array();
2640 if ( $skip_post_status ) {
2641 $where .= $post_type_where;
2642 } elseif ( ! empty( $query_vars['post_status'] ) ) {
2643
2644 $where .= $post_type_where;
2645
2646 $statuswheres = array();
2647 $q_status = $query_vars['post_status'];
2648 if ( ! is_array( $q_status ) ) {
2649 $q_status = explode( ',', $q_status );
2650 }
2651 sort( $q_status );
2652 $r_status = array();
2653 $p_status = array();
2654 $e_status = array();
2655 if ( in_array( 'any', $q_status, true ) ) {
2656 foreach ( get_post_stati( array( 'exclude_from_search' => true ) ) as $status ) {
2657 if ( ! in_array( $status, $q_status, true ) ) {
2658 $e_status[] = "{$wpdb->posts}.post_status <> '$status'";
2659 }
2660 }
2661 } else {
2662 foreach ( get_post_stati() as $status ) {
2663 if ( in_array( $status, $q_status, true ) ) {
2664 if ( 'private' === $status ) {
2665 $p_status[] = "{$wpdb->posts}.post_status = '$status'";
2666 } else {
2667 $r_status[] = "{$wpdb->posts}.post_status = '$status'";
2668 }
2669 }
2670 }
2671 }
2672
2673 if ( empty( $query_vars['perm'] ) || 'readable' !== $query_vars['perm'] ) {
2674 $r_status = array_merge( $r_status, $p_status );
2675 unset( $p_status );
2676 }
2677
2678 if ( ! empty( $e_status ) ) {
2679 $statuswheres[] = '(' . implode( ' AND ', $e_status ) . ')';
2680 }
2681 if ( ! empty( $r_status ) ) {
2682 if ( ! empty( $query_vars['perm'] ) && 'editable' === $query_vars['perm'] && ! current_user_can( $edit_others_cap ) ) {
2683 $statuswheres[] = "({$wpdb->posts}.post_author = $user_id " . 'AND (' . implode( ' OR ', $r_status ) . '))';
2684 } else {
2685 $statuswheres[] = '(' . implode( ' OR ', $r_status ) . ')';
2686 }
2687 }
2688 if ( ! empty( $p_status ) ) {
2689 if ( ! empty( $query_vars['perm'] ) && 'readable' === $query_vars['perm'] && ! current_user_can( $read_private_cap ) ) {
2690 $statuswheres[] = "({$wpdb->posts}.post_author = $user_id " . 'AND (' . implode( ' OR ', $p_status ) . '))';
2691 } else {
2692 $statuswheres[] = '(' . implode( ' OR ', $p_status ) . ')';
2693 }
2694 }
2695 if ( $post_status_join ) {
2696 $join .= " LEFT JOIN {$wpdb->posts} AS p2 ON ({$wpdb->posts}.post_parent = p2.ID) ";
2697 foreach ( $statuswheres as $index => $statuswhere ) {
2698 $statuswheres[ $index ] = "($statuswhere OR ({$wpdb->posts}.post_status = 'inherit' AND " . str_replace( $wpdb->posts, 'p2', $statuswhere ) . '))';
2699 }
2700 }
2701 $where_status = implode( ' OR ', $statuswheres );
2702 if ( ! empty( $where_status ) ) {
2703 $where .= " AND ($where_status)";
2704 }
2705 } elseif ( ! $this->is_singular ) {
2706 if ( 'any' === $post_type ) {
2707 $queried_post_types = get_post_types( array( 'exclude_from_search' => false ) );
2708 } elseif ( is_array( $post_type ) ) {
2709 $queried_post_types = $post_type;
2710 } elseif ( ! empty( $post_type ) ) {
2711 $queried_post_types = array( $post_type );
2712 } else {
2713 $queried_post_types = array( 'post' );
2714 }
2715
2716 if ( ! empty( $queried_post_types ) ) {
2717 sort( $queried_post_types );
2718 $status_type_clauses = array();
2719
2720 foreach ( $queried_post_types as $queried_post_type ) {
2721
2722 $queried_post_type_object = get_post_type_object( $queried_post_type );
2723
2724 $type_where = '(' . $wpdb->prepare( "{$wpdb->posts}.post_type = %s AND (", $queried_post_type );
2725
2726 // Public statuses.
2727 $public_statuses = get_post_stati( array( 'public' => true ) );
2728 $status_clauses = array();
2729 foreach ( $public_statuses as $public_status ) {
2730 $status_clauses[] = "{$wpdb->posts}.post_status = '$public_status'";
2731 }
2732 $type_where .= implode( ' OR ', $status_clauses );
2733
2734 // Add protected states that should show in the admin all list.
2735 if ( $this->is_admin ) {
2736 $admin_all_statuses = get_post_stati(
2737 array(
2738 'protected' => true,
2739 'show_in_admin_all_list' => true,
2740 )
2741 );
2742 foreach ( $admin_all_statuses as $admin_all_status ) {
2743 $type_where .= " OR {$wpdb->posts}.post_status = '$admin_all_status'";
2744 }
2745 }
2746
2747 // Add private states that are visible to current user.
2748 if ( is_user_logged_in() && $queried_post_type_object instanceof WP_Post_Type ) {
2749 $read_private_cap = $queried_post_type_object->cap->read_private_posts;
2750 $private_statuses = get_post_stati( array( 'private' => true ) );
2751 foreach ( $private_statuses as $private_status ) {
2752 $type_where .= current_user_can( $read_private_cap ) ? " \nOR {$wpdb->posts}.post_status = '$private_status'" : " \nOR ({$wpdb->posts}.post_author = $user_id AND {$wpdb->posts}.post_status = '$private_status')";
2753 }
2754 }
2755
2756 $type_where .= '))';
2757
2758 $status_type_clauses[] = $type_where;
2759 }
2760
2761 if ( ! empty( $status_type_clauses ) ) {
2762 $where .= ' AND (' . implode( ' OR ', $status_type_clauses ) . ')';
2763 }
2764 } else {
2765 $where .= ' AND 1=0 ';
2766 }
2767 } else {
2768 $where .= $post_type_where;
2769 }
2770
2771 /*
2772 * Apply filters on where and join prior to paging so that any
2773 * manipulations to them are reflected in the paging by day queries.
2774 */
2775 if ( ! $query_vars['suppress_filters'] ) {
2776 /**
2777 * Filters the WHERE clause of the query.
2778 *
2779 * @since 1.5.0
2780 *
2781 * @param string $where The WHERE clause of the query.
2782 * @param WP_Query $query The WP_Query instance (passed by reference).
2783 */
2784 $where = apply_filters_ref_array( 'posts_where', array( $where, &$this ) );
2785
2786 /**
2787 * Filters the JOIN clause of the query.
2788 *
2789 * @since 1.5.0
2790 *
2791 * @param string $join The JOIN clause of the query.
2792 * @param WP_Query $query The WP_Query instance (passed by reference).
2793 */
2794 $join = apply_filters_ref_array( 'posts_join', array( $join, &$this ) );
2795 }
2796
2797 // Paging.
2798 if ( empty( $query_vars['nopaging'] ) && ! $this->is_singular ) {
2799 $page = absint( $query_vars['paged'] );
2800 if ( ! $page ) {
2801 $page = 1;
2802 }
2803
2804 // If 'offset' is provided, it takes precedence over 'paged'.
2805 if ( isset( $query_vars['offset'] ) && is_numeric( $query_vars['offset'] ) ) {
2806 $query_vars['offset'] = absint( $query_vars['offset'] );
2807 $pgstrt = $query_vars['offset'] . ', ';
2808 } else {
2809 $pgstrt = absint( ( $page - 1 ) * $query_vars['posts_per_page'] ) . ', ';
2810 }
2811 $limits = 'LIMIT ' . $pgstrt . $query_vars['posts_per_page'];
2812 }
2813
2814 // Comments feeds.
2815 if ( $this->is_comment_feed && ! $this->is_singular ) {
2816 if ( $this->is_archive || $this->is_search ) {
2817 $cjoin = "JOIN {$wpdb->posts} ON ( {$wpdb->comments}.comment_post_ID = {$wpdb->posts}.ID ) $join ";
2818 $cwhere = "WHERE comment_approved = '1' $where";
2819 $cgroupby = "{$wpdb->comments}.comment_id";
2820 } else { // Other non-singular, e.g. front.
2821 $cjoin = "JOIN {$wpdb->posts} ON ( {$wpdb->comments}.comment_post_ID = {$wpdb->posts}.ID )";
2822 $cwhere = "WHERE ( post_status = 'publish' OR ( post_status = 'inherit' AND post_type = 'attachment' ) ) AND comment_approved = '1'";
2823 $cgroupby = '';
2824 }
2825
2826 if ( ! $query_vars['suppress_filters'] ) {
2827 /**
2828 * Filters the JOIN clause of the comments feed query before sending.
2829 *
2830 * @since 2.2.0
2831 *
2832 * @param string $cjoin The JOIN clause of the query.
2833 * @param WP_Query $query The WP_Query instance (passed by reference).
2834 */
2835 $cjoin = apply_filters_ref_array( 'comment_feed_join', array( $cjoin, &$this ) );
2836
2837 /**
2838 * Filters the WHERE clause of the comments feed query before sending.
2839 *
2840 * @since 2.2.0
2841 *
2842 * @param string $cwhere The WHERE clause of the query.
2843 * @param WP_Query $query The WP_Query instance (passed by reference).
2844 */
2845 $cwhere = apply_filters_ref_array( 'comment_feed_where', array( $cwhere, &$this ) );
2846
2847 /**
2848 * Filters the GROUP BY clause of the comments feed query before sending.
2849 *
2850 * @since 2.2.0
2851 *
2852 * @param string $cgroupby The GROUP BY clause of the query.
2853 * @param WP_Query $query The WP_Query instance (passed by reference).
2854 */
2855 $cgroupby = apply_filters_ref_array( 'comment_feed_groupby', array( $cgroupby, &$this ) );
2856
2857 /**
2858 * Filters the ORDER BY clause of the comments feed query before sending.
2859 *
2860 * @since 2.8.0
2861 *
2862 * @param string $corderby The ORDER BY clause of the query.
2863 * @param WP_Query $query The WP_Query instance (passed by reference).
2864 */
2865 $corderby = apply_filters_ref_array( 'comment_feed_orderby', array( 'comment_date_gmt DESC', &$this ) );
2866
2867 /**
2868 * Filters the LIMIT clause of the comments feed query before sending.
2869 *
2870 * @since 2.8.0
2871 *
2872 * @param string $climits The JOIN clause of the query.
2873 * @param WP_Query $query The WP_Query instance (passed by reference).
2874 */
2875 $climits = apply_filters_ref_array( 'comment_feed_limits', array( 'LIMIT ' . get_option( 'posts_per_rss' ), &$this ) );
2876 }
2877
2878 $cgroupby = ( ! empty( $cgroupby ) ) ? 'GROUP BY ' . $cgroupby : '';
2879 $corderby = ( ! empty( $corderby ) ) ? 'ORDER BY ' . $corderby : '';
2880 $climits = ( ! empty( $climits ) ) ? $climits : '';
2881
2882 $comments_request = "SELECT $distinct {$wpdb->comments}.comment_ID FROM {$wpdb->comments} $cjoin $cwhere $cgroupby $corderby $climits";
2883
2884 $key = md5( $comments_request );
2885 $last_changed = array(
2886 wp_cache_get_last_changed( 'comment' ),
2887 wp_cache_get_last_changed( 'posts' ),
2888 );
2889
2890 $cache_key = "comment_feed:$key";
2891 $comment_ids = wp_cache_get_salted( $cache_key, 'comment-queries', $last_changed );
2892 if ( false === $comment_ids ) {
2893 $comment_ids = $wpdb->get_col( $comments_request );
2894 wp_cache_set_salted( $cache_key, $comment_ids, 'comment-queries', $last_changed );
2895 }
2896 _prime_comment_caches( $comment_ids );
2897
2898 // Convert to WP_Comment.
2899 /** @var WP_Comment[] */
2900 $this->comments = array_map( 'get_comment', $comment_ids );
2901 $this->comment_count = count( $this->comments );
2902
2903 $post_ids = array();
2904
2905 foreach ( $this->comments as $comment ) {
2906 $post_ids[] = (int) $comment->comment_post_ID;
2907 }
2908
2909 $post_ids = implode( ',', $post_ids );
2910 $join = '';
2911 if ( $post_ids ) {
2912 $where = "AND {$wpdb->posts}.ID IN ($post_ids) ";
2913 } else {
2914 $where = 'AND 0';
2915 }
2916 }
2917
2918 $pieces = array( 'where', 'groupby', 'join', 'orderby', 'distinct', 'fields', 'limits' );
2919
2920 /*
2921 * Apply post-paging filters on where and join. Only plugins that
2922 * manipulate paging queries should use these hooks.
2923 */
2924 if ( ! $query_vars['suppress_filters'] ) {
2925 /**
2926 * Filters the WHERE clause of the query.
2927 *
2928 * Specifically for manipulating paging queries.
2929 *
2930 * @since 1.5.0
2931 *
2932 * @param string $where The WHERE clause of the query.
2933 * @param WP_Query $query The WP_Query instance (passed by reference).
2934 */
2935 $where = apply_filters_ref_array( 'posts_where_paged', array( $where, &$this ) );
2936
2937 /**
2938 * Filters the GROUP BY clause of the query.
2939 *
2940 * @since 2.0.0
2941 *
2942 * @param string $groupby The GROUP BY clause of the query.
2943 * @param WP_Query $query The WP_Query instance (passed by reference).
2944 */
2945 $groupby = apply_filters_ref_array( 'posts_groupby', array( $groupby, &$this ) );
2946
2947 /**
2948 * Filters the JOIN clause of the query.
2949 *
2950 * Specifically for manipulating paging queries.
2951 *
2952 * @since 1.5.0
2953 *
2954 * @param string $join The JOIN clause of the query.
2955 * @param WP_Query $query The WP_Query instance (passed by reference).
2956 */
2957 $join = apply_filters_ref_array( 'posts_join_paged', array( $join, &$this ) );
2958
2959 /**
2960 * Filters the ORDER BY clause of the query.
2961 *
2962 * @since 1.5.1
2963 *
2964 * @param string $orderby The ORDER BY clause of the query.
2965 * @param WP_Query $query The WP_Query instance (passed by reference).
2966 */
2967 $orderby = apply_filters_ref_array( 'posts_orderby', array( $orderby, &$this ) );
2968
2969 /**
2970 * Filters the DISTINCT clause of the query.
2971 *
2972 * @since 2.1.0
2973 *
2974 * @param string $distinct The DISTINCT clause of the query.
2975 * @param WP_Query $query The WP_Query instance (passed by reference).
2976 */
2977 $distinct = apply_filters_ref_array( 'posts_distinct', array( $distinct, &$this ) );
2978
2979 /**
2980 * Filters the LIMIT clause of the query.
2981 *
2982 * @since 2.1.0
2983 *
2984 * @param string $limits The LIMIT clause of the query.
2985 * @param WP_Query $query The WP_Query instance (passed by reference).
2986 */
2987 $limits = apply_filters_ref_array( 'post_limits', array( $limits, &$this ) );
2988
2989 /**
2990 * Filters the SELECT clause of the query.
2991 *
2992 * @since 2.1.0
2993 *
2994 * @param string $fields The SELECT clause of the query.
2995 * @param WP_Query $query The WP_Query instance (passed by reference).
2996 */
2997 $fields = apply_filters_ref_array( 'posts_fields', array( $fields, &$this ) );
2998
2999 /**
3000 * Filters all query clauses at once, for convenience.
3001 *
3002 * Covers the WHERE, GROUP BY, JOIN, ORDER BY, DISTINCT,
3003 * fields (SELECT), and LIMIT clauses.
3004 *
3005 * @since 3.1.0
3006 *
3007 * @param string[] $clauses {
3008 * Associative array of the clauses for the query.
3009 *
3010 * @type string $where The WHERE clause of the query.
3011 * @type string $groupby The GROUP BY clause of the query.
3012 * @type string $join The JOIN clause of the query.
3013 * @type string $orderby The ORDER BY clause of the query.
3014 * @type string $distinct The DISTINCT clause of the query.
3015 * @type string $fields The SELECT clause of the query.
3016 * @type string $limits The LIMIT clause of the query.
3017 * }
3018 * @param WP_Query $query The WP_Query instance (passed by reference).
3019 */
3020 $clauses = (array) apply_filters_ref_array( 'posts_clauses', array( compact( $pieces ), &$this ) );
3021
3022 $where = isset( $clauses['where'] ) ? $clauses['where'] : '';
3023 $groupby = isset( $clauses['groupby'] ) ? $clauses['groupby'] : '';
3024 $join = isset( $clauses['join'] ) ? $clauses['join'] : '';
3025 $orderby = isset( $clauses['orderby'] ) ? $clauses['orderby'] : '';
3026 $distinct = isset( $clauses['distinct'] ) ? $clauses['distinct'] : '';
3027 $fields = isset( $clauses['fields'] ) ? $clauses['fields'] : '';
3028 $limits = isset( $clauses['limits'] ) ? $clauses['limits'] : '';
3029 }
3030
3031 /**
3032 * Fires to announce the query's current selection parameters.
3033 *
3034 * For use by caching plugins.
3035 *
3036 * @since 2.3.0
3037 *
3038 * @param string $selection The assembled selection query.
3039 */
3040 do_action( 'posts_selection', $where . $groupby . $orderby . $limits . $join );
3041
3042 /*
3043 * Filters again for the benefit of caching plugins.
3044 * Regular plugins should use the hooks above.
3045 */
3046 if ( ! $query_vars['suppress_filters'] ) {
3047 /**
3048 * Filters the WHERE clause of the query.
3049 *
3050 * For use by caching plugins.
3051 *
3052 * @since 2.5.0
3053 *
3054 * @param string $where The WHERE clause of the query.
3055 * @param WP_Query $query The WP_Query instance (passed by reference).
3056 */
3057 $where = apply_filters_ref_array( 'posts_where_request', array( $where, &$this ) );
3058
3059 /**
3060 * Filters the GROUP BY clause of the query.
3061 *
3062 * For use by caching plugins.
3063 *
3064 * @since 2.5.0
3065 *
3066 * @param string $groupby The GROUP BY clause of the query.
3067 * @param WP_Query $query The WP_Query instance (passed by reference).
3068 */
3069 $groupby = apply_filters_ref_array( 'posts_groupby_request', array( $groupby, &$this ) );
3070
3071 /**
3072 * Filters the JOIN clause of the query.
3073 *
3074 * For use by caching plugins.
3075 *
3076 * @since 2.5.0
3077 *
3078 * @param string $join The JOIN clause of the query.
3079 * @param WP_Query $query The WP_Query instance (passed by reference).
3080 */
3081 $join = apply_filters_ref_array( 'posts_join_request', array( $join, &$this ) );
3082
3083 /**
3084 * Filters the ORDER BY clause of the query.
3085 *
3086 * For use by caching plugins.
3087 *
3088 * @since 2.5.0
3089 *
3090 * @param string $orderby The ORDER BY clause of the query.
3091 * @param WP_Query $query The WP_Query instance (passed by reference).
3092 */
3093 $orderby = apply_filters_ref_array( 'posts_orderby_request', array( $orderby, &$this ) );
3094
3095 /**
3096 * Filters the DISTINCT clause of the query.
3097 *
3098 * For use by caching plugins.
3099 *
3100 * @since 2.5.0
3101 *
3102 * @param string $distinct The DISTINCT clause of the query.
3103 * @param WP_Query $query The WP_Query instance (passed by reference).
3104 */
3105 $distinct = apply_filters_ref_array( 'posts_distinct_request', array( $distinct, &$this ) );
3106
3107 /**
3108 * Filters the SELECT clause of the query.
3109 *
3110 * For use by caching plugins.
3111 *
3112 * @since 2.5.0
3113 *
3114 * @param string $fields The SELECT clause of the query.
3115 * @param WP_Query $query The WP_Query instance (passed by reference).
3116 */
3117 $fields = apply_filters_ref_array( 'posts_fields_request', array( $fields, &$this ) );
3118
3119 /**
3120 * Filters the LIMIT clause of the query.
3121 *
3122 * For use by caching plugins.
3123 *
3124 * @since 2.5.0
3125 *
3126 * @param string $limits The LIMIT clause of the query.
3127 * @param WP_Query $query The WP_Query instance (passed by reference).
3128 */
3129 $limits = apply_filters_ref_array( 'post_limits_request', array( $limits, &$this ) );
3130
3131 /**
3132 * Filters all query clauses at once, for convenience.
3133 *
3134 * For use by caching plugins.
3135 *
3136 * Covers the WHERE, GROUP BY, JOIN, ORDER BY, DISTINCT,
3137 * fields (SELECT), and LIMIT clauses.
3138 *
3139 * @since 3.1.0
3140 *
3141 * @param string[] $clauses {
3142 * Associative array of the clauses for the query.
3143 *
3144 * @type string $where The WHERE clause of the query.
3145 * @type string $groupby The GROUP BY clause of the query.
3146 * @type string $join The JOIN clause of the query.
3147 * @type string $orderby The ORDER BY clause of the query.
3148 * @type string $distinct The DISTINCT clause of the query.
3149 * @type string $fields The SELECT clause of the query.
3150 * @type string $limits The LIMIT clause of the query.
3151 * }
3152 * @param WP_Query $query The WP_Query instance (passed by reference).
3153 */
3154 $clauses = (array) apply_filters_ref_array( 'posts_clauses_request', array( compact( $pieces ), &$this ) );
3155
3156 $where = isset( $clauses['where'] ) ? $clauses['where'] : '';
3157 $groupby = isset( $clauses['groupby'] ) ? $clauses['groupby'] : '';
3158 $join = isset( $clauses['join'] ) ? $clauses['join'] : '';
3159 $orderby = isset( $clauses['orderby'] ) ? $clauses['orderby'] : '';
3160 $distinct = isset( $clauses['distinct'] ) ? $clauses['distinct'] : '';
3161 $fields = isset( $clauses['fields'] ) ? $clauses['fields'] : '';
3162 $limits = isset( $clauses['limits'] ) ? $clauses['limits'] : '';
3163 }
3164
3165 if ( ! empty( $groupby ) ) {
3166 $groupby = 'GROUP BY ' . $groupby;
3167 }
3168 if ( ! empty( $orderby ) ) {
3169 $orderby = 'ORDER BY ' . $orderby;
3170 }
3171
3172 $found_rows = '';
3173 if ( ! $query_vars['no_found_rows'] && ! empty( $limits ) ) {
3174 $found_rows = 'SQL_CALC_FOUND_ROWS';
3175 }
3176
3177 /*
3178 * Beginning of the string is on a new line to prevent leading whitespace.
3179 *
3180 * The additional indentation of subsequent lines is to ensure the SQL
3181 * queries are identical to those generated when splitting queries. This
3182 * improves caching of the query by ensuring the same cache key is
3183 * generated for the same database queries functionally.
3184 *
3185 * See https://core.trac.wordpress.org/ticket/56841.
3186 * See https://github.com/WordPress/wordpress-develop/pull/6393#issuecomment-2088217429
3187 */
3188 $old_request =
3189 "SELECT $found_rows $distinct $fields
3190 FROM {$wpdb->posts} $join
3191 WHERE 1=1 $where
3192 $groupby
3193 $orderby
3194 $limits";
3195
3196 $this->request = $old_request;
3197
3198 if ( ! $query_vars['suppress_filters'] ) {
3199 /**
3200 * Filters the completed SQL query before sending.
3201 *
3202 * @since 2.0.0
3203 *
3204 * @param string $request The complete SQL query.
3205 * @param WP_Query $query The WP_Query instance (passed by reference).
3206 */
3207 $this->request = apply_filters_ref_array( 'posts_request', array( $this->request, &$this ) );
3208 }
3209
3210 /**
3211 * Filters the posts array before the query takes place.
3212 *
3213 * Return a non-null value to bypass WordPress' default post queries.
3214 *
3215 * Filtering functions that require pagination information are encouraged to set
3216 * the `found_posts` and `max_num_pages` properties of the WP_Query object,
3217 * passed to the filter by reference. If WP_Query does not perform a database
3218 * query, it will not have enough information to generate these values itself.
3219 *
3220 * @since 4.6.0
3221 *
3222 * @param WP_Post[]|int[]|null $posts Return an array of post data to short-circuit WP's query,
3223 * or null to allow WP to run its normal queries.
3224 * @param WP_Query $query The WP_Query instance (passed by reference).
3225 */
3226 $this->posts = apply_filters_ref_array( 'posts_pre_query', array( null, &$this ) );
3227
3228 /*
3229 * Ensure the ID database query is able to be cached.
3230 *
3231 * Random queries are expected to have unpredictable results and
3232 * cannot be cached. Note the space before `RAND` in the string
3233 * search, that to ensure against a collision with another
3234 * function.
3235 *
3236 * If `$fields` has been modified by the `posts_fields`,
3237 * `posts_fields_request`, `post_clauses` or `posts_clauses_request`
3238 * filters, then caching is disabled to prevent caching collisions.
3239 */
3240 $id_query_is_cacheable = ! str_contains( strtoupper( $orderby ), ' RAND(' );
3241
3242 $cacheable_field_values = array(
3243 "{$wpdb->posts}.*",
3244 "{$wpdb->posts}.ID, {$wpdb->posts}.post_parent",
3245 "{$wpdb->posts}.ID",
3246 );
3247
3248 if ( ! in_array( $fields, $cacheable_field_values, true ) ) {
3249 $id_query_is_cacheable = false;
3250 }
3251
3252 $last_changed = (array) wp_cache_get_last_changed( 'posts' );
3253 if ( ! empty( $this->tax_query->queries ) ) {
3254 $last_changed[] = wp_cache_get_last_changed( 'terms' );
3255 }
3256
3257 if ( $query_vars['cache_results'] && $id_query_is_cacheable ) {
3258 $new_request = str_replace( $fields, "{$wpdb->posts}.*", $this->request );
3259 $cache_key = $this->generate_cache_key( $query_vars, $new_request );
3260
3261 $cache_found = false;
3262 if ( null === $this->posts ) {
3263 $cached_results = wp_cache_get_salted( $cache_key, 'post-queries', $last_changed );
3264
3265 if ( $cached_results ) {
3266 $cache_found = true;
3267 /** @var int[] */
3268 $post_ids = array_map( 'intval', $cached_results['posts'] );
3269
3270 $this->post_count = count( $post_ids );
3271 $this->found_posts = $cached_results['found_posts'];
3272 $this->max_num_pages = $cached_results['max_num_pages'];
3273
3274 if ( 'ids' === $query_vars['fields'] ) {
3275 $this->posts = $post_ids;
3276
3277 return $this->posts;
3278 } elseif ( 'id=>parent' === $query_vars['fields'] ) {
3279 _prime_post_parent_id_caches( $post_ids );
3280
3281 $post_parent_cache_keys = array();
3282 foreach ( $post_ids as $post_id ) {
3283 $post_parent_cache_keys[] = 'post_parent:' . (string) $post_id;
3284 }
3285
3286 /** @var int[] */
3287 $post_parents = wp_cache_get_multiple( $post_parent_cache_keys, 'posts' );
3288
3289 foreach ( $post_parents as $cache_key => $post_parent ) {
3290 $obj = new stdClass();
3291 $obj->ID = (int) str_replace( 'post_parent:', '', $cache_key );
3292 $obj->post_parent = (int) $post_parent;
3293
3294 $this->posts[] = $obj;
3295 }
3296
3297 return $post_parents;
3298 } else {
3299 _prime_post_caches( $post_ids, $query_vars['update_post_term_cache'], $query_vars['update_post_meta_cache'] );
3300 /** @var WP_Post[] */
3301 $this->posts = array_map( 'get_post', $post_ids );
3302 }
3303 }
3304 }
3305 }
3306
3307 if ( 'ids' === $query_vars['fields'] ) {
3308 if ( null === $this->posts ) {
3309 $this->posts = $wpdb->get_col( $this->request );
3310 }
3311
3312 /** @var int[] */
3313 $this->posts = array_map( 'intval', $this->posts );
3314 $this->post_count = count( $this->posts );
3315 $this->set_found_posts( $query_vars, $limits );
3316
3317 if ( $query_vars['cache_results'] && $id_query_is_cacheable ) {
3318 $cache_value = array(
3319 'posts' => $this->posts,
3320 'found_posts' => $this->found_posts,
3321 'max_num_pages' => $this->max_num_pages,
3322 );
3323
3324 wp_cache_set_salted( $cache_key, $cache_value, 'post-queries', $last_changed );
3325 }
3326
3327 return $this->posts;
3328 }
3329
3330 if ( 'id=>parent' === $query_vars['fields'] ) {
3331 if ( null === $this->posts ) {
3332 $this->posts = $wpdb->get_results( $this->request );
3333 }
3334
3335 $this->post_count = count( $this->posts );
3336 $this->set_found_posts( $query_vars, $limits );
3337
3338 /** @var int[] */
3339 $post_parents = array();
3340 $post_ids = array();
3341 $post_parents_cache = array();
3342
3343 foreach ( $this->posts as $key => $post ) {
3344 $this->posts[ $key ]->ID = (int) $post->ID;
3345 $this->posts[ $key ]->post_parent = (int) $post->post_parent;
3346
3347 $post_parents[ (int) $post->ID ] = (int) $post->post_parent;
3348 $post_ids[] = (int) $post->ID;
3349
3350 $post_parents_cache[ 'post_parent:' . (string) $post->ID ] = (int) $post->post_parent;
3351 }
3352 // Prime post parent caches, so that on second run, there is not another database query.
3353 wp_cache_add_multiple( $post_parents_cache, 'posts' );
3354
3355 if ( $query_vars['cache_results'] && $id_query_is_cacheable ) {
3356 $cache_value = array(
3357 'posts' => $post_ids,
3358 'found_posts' => $this->found_posts,
3359 'max_num_pages' => $this->max_num_pages,
3360 );
3361
3362 wp_cache_set_salted( $cache_key, $cache_value, 'post-queries', $last_changed );
3363 }
3364
3365 return $post_parents;
3366 }
3367
3368 $is_unfiltered_query = $old_request === $this->request && "{$wpdb->posts}.*" === $fields;
3369
3370 if ( null === $this->posts ) {
3371 $split_the_query = (
3372 $is_unfiltered_query
3373 && (
3374 wp_using_ext_object_cache()
3375 || ( ! empty( $limits ) && $query_vars['posts_per_page'] < 500 )
3376 )
3377 );
3378
3379 /**
3380 * Filters whether to split the query.
3381 *
3382 * Splitting the query will cause it to fetch just the IDs of the found posts
3383 * (and then individually fetch each post by ID), rather than fetching every
3384 * complete row at once. One massive result vs. many small results.
3385 *
3386 * @since 3.4.0
3387 * @since 6.6.0 Added the `$old_request` and `$clauses` parameters.
3388 *
3389 * @param bool $split_the_query Whether or not to split the query.
3390 * @param WP_Query $query The WP_Query instance.
3391 * @param string $old_request The complete SQL query before filtering.
3392 * @param string[] $clauses {
3393 * Associative array of the clauses for the query.
3394 *
3395 * @type string $where The WHERE clause of the query.
3396 * @type string $groupby The GROUP BY clause of the query.
3397 * @type string $join The JOIN clause of the query.
3398 * @type string $orderby The ORDER BY clause of the query.
3399 * @type string $distinct The DISTINCT clause of the query.
3400 * @type string $fields The SELECT clause of the query.
3401 * @type string $limits The LIMIT clause of the query.
3402 * }
3403 */
3404 $split_the_query = apply_filters( 'split_the_query', $split_the_query, $this, $old_request, compact( $pieces ) );
3405
3406 if ( $split_the_query ) {
3407 // First get the IDs and then fill in the objects.
3408
3409 // Beginning of the string is on a new line to prevent leading whitespace. See https://core.trac.wordpress.org/ticket/56841.
3410 $this->request =
3411 "SELECT $found_rows $distinct {$wpdb->posts}.ID
3412 FROM {$wpdb->posts} $join
3413 WHERE 1=1 $where
3414 $groupby
3415 $orderby
3416 $limits";
3417
3418 /**
3419 * Filters the Post IDs SQL request before sending.
3420 *
3421 * @since 3.4.0
3422 *
3423 * @param string $request The post ID request.
3424 * @param WP_Query $query The WP_Query instance.
3425 */
3426 $this->request = apply_filters( 'posts_request_ids', $this->request, $this );
3427
3428 $post_ids = $wpdb->get_col( $this->request );
3429
3430 if ( $post_ids ) {
3431 $this->posts = $post_ids;
3432 $this->set_found_posts( $query_vars, $limits );
3433 _prime_post_caches( $post_ids, $query_vars['update_post_term_cache'], $query_vars['update_post_meta_cache'] );
3434 } else {
3435 $this->posts = array();
3436 }
3437 } else {
3438 $this->posts = $wpdb->get_results( $this->request );
3439 $this->set_found_posts( $query_vars, $limits );
3440 }
3441 }
3442
3443 // Convert to WP_Post objects.
3444 if ( $this->posts ) {
3445 $this->posts = apply_filters('godaddy/wp_query/get_posts/before_get_post', $this->posts, $this);
3446
3447 /** @var WP_Post[] */
3448 $this->posts = array_map( 'get_post', $this->posts );
3449 }
3450
3451 $unfiltered_posts = $this->posts;
3452
3453 if ( $query_vars['cache_results'] && $id_query_is_cacheable && ! $cache_found ) {
3454 $post_ids = wp_list_pluck( $this->posts, 'ID' );
3455
3456 $cache_value = array(
3457 'posts' => $post_ids,
3458 'found_posts' => $this->found_posts,
3459 'max_num_pages' => $this->max_num_pages,
3460 );
3461
3462 wp_cache_set_salted( $cache_key, $cache_value, 'post-queries', $last_changed );
3463 }
3464
3465 if ( ! $query_vars['suppress_filters'] ) {
3466 /**
3467 * Filters the raw post results array, prior to status checks.
3468 *
3469 * @since 2.3.0
3470 *
3471 * @param WP_Post[] $posts Array of post objects.
3472 * @param WP_Query $query The WP_Query instance (passed by reference).
3473 */
3474 $this->posts = apply_filters_ref_array( 'posts_results', array( $this->posts, &$this ) );
3475 }
3476
3477 if ( ! empty( $this->posts ) && $this->is_comment_feed && $this->is_singular ) {
3478 /** This filter is documented in wp-includes/query.php */
3479 $cjoin = apply_filters_ref_array( 'comment_feed_join', array( '', &$this ) );
3480
3481 /** This filter is documented in wp-includes/query.php */
3482 $cwhere = apply_filters_ref_array( 'comment_feed_where', array( "WHERE comment_post_ID = '{$this->posts[0]->ID}' AND comment_approved = '1'", &$this ) );
3483
3484 /** This filter is documented in wp-includes/query.php */
3485 $cgroupby = apply_filters_ref_array( 'comment_feed_groupby', array( '', &$this ) );
3486 $cgroupby = ( ! empty( $cgroupby ) ) ? 'GROUP BY ' . $cgroupby : '';
3487
3488 /** This filter is documented in wp-includes/query.php */
3489 $corderby = apply_filters_ref_array( 'comment_feed_orderby', array( 'comment_date_gmt DESC', &$this ) );
3490 $corderby = ( ! empty( $corderby ) ) ? 'ORDER BY ' . $corderby : '';
3491
3492 /** This filter is documented in wp-includes/query.php */
3493 $climits = apply_filters_ref_array( 'comment_feed_limits', array( 'LIMIT ' . get_option( 'posts_per_rss' ), &$this ) );
3494
3495 $comments_request = "SELECT {$wpdb->comments}.comment_ID FROM {$wpdb->comments} $cjoin $cwhere $cgroupby $corderby $climits";
3496
3497 $comment_key = md5( $comments_request );
3498 $comment_last_changed = wp_cache_get_last_changed( 'comment' );
3499
3500 $comment_cache_key = "comment_feed:$comment_key";
3501 $comment_ids = wp_cache_get_salted( $comment_cache_key, 'comment-queries', $comment_last_changed );
3502 if ( false === $comment_ids ) {
3503 $comment_ids = $wpdb->get_col( $comments_request );
3504 wp_cache_set_salted( $comment_cache_key, $comment_ids, 'comment-queries', $comment_last_changed );
3505 }
3506 _prime_comment_caches( $comment_ids );
3507
3508 // Convert to WP_Comment.
3509 /** @var WP_Comment[] */
3510 $this->comments = array_map( 'get_comment', $comment_ids );
3511 $this->comment_count = count( $this->comments );
3512 }
3513
3514 // Check post status to determine if post should be displayed.
3515 if ( ! empty( $this->posts ) && ( $this->is_single || $this->is_page ) ) {
3516 $status = get_post_status( $this->posts[0] );
3517
3518 if ( 'attachment' === $this->posts[0]->post_type && 0 === (int) $this->posts[0]->post_parent ) {
3519 $this->is_page = false;
3520 $this->is_single = true;
3521 $this->is_attachment = true;
3522 }
3523
3524 // If the post_status was specifically requested, let it pass through.
3525 if ( ! in_array( $status, $q_status, true ) ) {
3526 $post_status_obj = get_post_status_object( $status );
3527
3528 if ( $post_status_obj && ! $post_status_obj->public ) {
3529 if ( ! is_user_logged_in() ) {
3530 // User must be logged in to view unpublished posts.
3531 $this->posts = array();
3532 } else {
3533 if ( $post_status_obj->protected ) {
3534 // User must have edit permissions on the draft to preview.
3535 if ( ! current_user_can( $edit_cap, $this->posts[0]->ID ) ) {
3536 $this->posts = array();
3537 } else {
3538 $this->is_preview = true;
3539 if ( 'future' !== $status ) {
3540 $this->posts[0]->post_date = current_time( 'mysql' );
3541 }
3542 }
3543 } elseif ( $post_status_obj->private ) {
3544 if ( ! current_user_can( $read_cap, $this->posts[0]->ID ) ) {
3545 $this->posts = array();
3546 }
3547 } else {
3548 $this->posts = array();
3549 }
3550 }
3551 } elseif ( ! $post_status_obj ) {
3552 // Post status is not registered, assume it's not public.
3553 if ( ! current_user_can( $edit_cap, $this->posts[0]->ID ) ) {
3554 $this->posts = array();
3555 }
3556 }
3557 }
3558
3559 if ( $this->is_preview && $this->posts && current_user_can( $edit_cap, $this->posts[0]->ID ) ) {
3560 /**
3561 * Filters the single post for preview mode.
3562 *
3563 * @since 2.7.0
3564 *
3565 * @param WP_Post $post_preview The Post object.
3566 * @param WP_Query $query The WP_Query instance (passed by reference).
3567 */
3568 $this->posts[0] = get_post( apply_filters_ref_array( 'the_preview', array( $this->posts[0], &$this ) ) );
3569 }
3570 }
3571
3572 // Put sticky posts at the top of the posts array.
3573 $sticky_posts = get_option( 'sticky_posts' );
3574 if ( $this->is_home && $page <= 1 && is_array( $sticky_posts ) && ! empty( $sticky_posts ) && ! $query_vars['ignore_sticky_posts'] ) {
3575 $num_posts = count( $this->posts );
3576 $sticky_offset = 0;
3577 // Loop over posts and relocate stickies to the front.
3578 for ( $i = 0; $i < $num_posts; $i++ ) {
3579 if ( in_array( $this->posts[ $i ]->ID, $sticky_posts, true ) ) {
3580 $sticky_post = $this->posts[ $i ];
3581 // Remove sticky from current position.
3582 array_splice( $this->posts, $i, 1 );
3583 // Move to front, after other stickies.
3584 array_splice( $this->posts, $sticky_offset, 0, array( $sticky_post ) );
3585 // Increment the sticky offset. The next sticky will be placed at this offset.
3586 ++$sticky_offset;
3587 // Remove post from sticky posts array.
3588 $offset = array_search( $sticky_post->ID, $sticky_posts, true );
3589 unset( $sticky_posts[ $offset ] );
3590 }
3591 }
3592
3593 // If any posts have been excluded specifically, Ignore those that are sticky.
3594 if ( ! empty( $sticky_posts ) && ! empty( $query_vars['post__not_in'] ) ) {
3595 $sticky_posts = array_diff( $sticky_posts, $query_vars['post__not_in'] );
3596 }
3597
3598 // Fetch sticky posts that weren't in the query results.
3599 if ( ! empty( $sticky_posts ) ) {
3600 $stickies = get_posts(
3601 array(
3602 'post__in' => $sticky_posts,
3603 'post_type' => $post_type,
3604 'post_status' => 'publish',
3605 'posts_per_page' => count( $sticky_posts ),
3606 'suppress_filters' => $query_vars['suppress_filters'],
3607 'cache_results' => $query_vars['cache_results'],
3608 'update_post_meta_cache' => $query_vars['update_post_meta_cache'],
3609 'update_post_term_cache' => $query_vars['update_post_term_cache'],
3610 'lazy_load_term_meta' => $query_vars['lazy_load_term_meta'],
3611 )
3612 );
3613
3614 foreach ( $stickies as $sticky_post ) {
3615 array_splice( $this->posts, $sticky_offset, 0, array( $sticky_post ) );
3616 ++$sticky_offset;
3617 }
3618 }
3619 }
3620
3621 if ( ! $query_vars['suppress_filters'] ) {
3622 /**
3623 * Filters the array of retrieved posts after they've been fetched and
3624 * internally processed.
3625 *
3626 * @since 1.5.0
3627 *
3628 * @param WP_Post[] $posts Array of post objects.
3629 * @param WP_Query $query The WP_Query instance (passed by reference).
3630 */
3631 $this->posts = apply_filters_ref_array( 'the_posts', array( $this->posts, &$this ) );
3632 }
3633
3634 /*
3635 * Ensure that any posts added/modified via one of the filters above are
3636 * of the type WP_Post and are filtered.
3637 */
3638 if ( $this->posts ) {
3639 $this->post_count = count( $this->posts );
3640
3641 /** @var WP_Post[] */
3642 $this->posts = array_map( 'get_post', $this->posts );
3643
3644 if ( $query_vars['cache_results'] ) {
3645 if ( $is_unfiltered_query && $unfiltered_posts === $this->posts ) {
3646 update_post_caches( $this->posts, $post_type, $query_vars['update_post_term_cache'], $query_vars['update_post_meta_cache'] );
3647 } else {
3648 $post_ids = wp_list_pluck( $this->posts, 'ID' );
3649 _prime_post_caches( $post_ids, $query_vars['update_post_term_cache'], $query_vars['update_post_meta_cache'] );
3650 }
3651 }
3652
3653 /** @var WP_Post */
3654 $this->post = reset( $this->posts );
3655 } else {
3656 $this->post_count = 0;
3657 $this->posts = array();
3658 }
3659
3660 if ( ! empty( $this->posts ) && $query_vars['update_menu_item_cache'] ) {
3661 update_menu_item_cache( $this->posts );
3662 }
3663
3664 if ( $query_vars['lazy_load_term_meta'] ) {
3665 wp_queue_posts_for_term_meta_lazyload( $this->posts );
3666 }
3667
3668 return $this->posts;
3669 }
3670
3671 /**
3672 * Sets up the amount of found posts and the number of pages (if limit clause was used)
3673 * for the current query.
3674 *
3675 * @since 3.5.0
3676 *
3677 * @global wpdb $wpdb WordPress database abstraction object.
3678 *
3679 * @param array $query_vars Query variables.
3680 * @param string $limits LIMIT clauses of the query.
3681 */
3682 private function set_found_posts( $query_vars, $limits ) {
3683 global $wpdb;
3684
3685 /*
3686 * Bail if posts is an empty array. Continue if posts is an empty string,
3687 * null, or false to accommodate caching plugins that fill posts later.
3688 */
3689 if ( $query_vars['no_found_rows'] || ( is_array( $this->posts ) && ! $this->posts ) ) {
3690 return;
3691 }
3692
3693 if ( ! empty( $limits ) ) {
3694 /**
3695 * Filters the query to run for retrieving the found posts.
3696 *
3697 * @since 2.1.0
3698 *
3699 * @param string $found_posts_query The query to run to find the found posts.
3700 * @param WP_Query $query The WP_Query instance (passed by reference).
3701 */
3702 $found_posts_query = apply_filters_ref_array( 'found_posts_query', array( 'SELECT FOUND_ROWS()', &$this ) );
3703
3704 $this->found_posts = (int) $wpdb->get_var( $found_posts_query );
3705 } else {
3706 if ( is_array( $this->posts ) ) {
3707 $this->found_posts = count( $this->posts );
3708 } else {
3709 if ( null === $this->posts ) {
3710 $this->found_posts = 0;
3711 } else {
3712 $this->found_posts = 1;
3713 }
3714 }
3715 }
3716
3717 /**
3718 * Filters the number of found posts for the query.
3719 *
3720 * @since 2.1.0
3721 *
3722 * @param int $found_posts The number of posts found.
3723 * @param WP_Query $query The WP_Query instance (passed by reference).
3724 */
3725 $this->found_posts = (int) apply_filters_ref_array( 'found_posts', array( $this->found_posts, &$this ) );
3726
3727 if ( ! empty( $limits ) ) {
3728 $this->max_num_pages = (int) ceil( $this->found_posts / $query_vars['posts_per_page'] );
3729 }
3730 }
3731
3732 /**
3733 * Sets up the next post and iterate current post index.
3734 *
3735 * @since 1.5.0
3736 *
3737 * @return WP_Post Next post.
3738 */
3739 public function next_post() {
3740
3741 ++$this->current_post;
3742
3743 /** @var WP_Post */
3744 $this->post = $this->posts[ $this->current_post ];
3745 return $this->post;
3746 }
3747
3748 /**
3749 * Sets up the current post.
3750 *
3751 * Retrieves the next post, sets up the post, sets the 'in the loop'
3752 * property to true.
3753 *
3754 * @since 1.5.0
3755 *
3756 * @global WP_Post $post Global post object.
3757 */
3758 public function the_post() {
3759 global $post;
3760
3761 if ( ! $this->in_the_loop ) {
3762 if ( 'all' === $this->query_vars['fields'] ) {
3763 // Full post objects queried.
3764 $post_objects = $this->posts;
3765 } else {
3766 if ( 'ids' === $this->query_vars['fields'] ) {
3767 // Post IDs queried.
3768 $post_ids = $this->posts;
3769 } else {
3770 // Only partial objects queried, need to prime the cache for the loop.
3771 $post_ids = array_reduce(
3772 $this->posts,
3773 function ( $carry, $post ) {
3774 if ( isset( $post->ID ) ) {
3775 $carry[] = $post->ID;
3776 }
3777
3778 return $carry;
3779 },
3780 array()
3781 );
3782 }
3783 _prime_post_caches( $post_ids, $this->query_vars['update_post_term_cache'], $this->query_vars['update_post_meta_cache'] );
3784 $post_objects = array_map( 'get_post', $post_ids );
3785 }
3786 update_post_author_caches( $post_objects );
3787 }
3788
3789 $this->in_the_loop = true;
3790 $this->before_loop = false;
3791
3792 if ( -1 === $this->current_post ) { // Loop has just started.
3793 /**
3794 * Fires once the loop is started.
3795 *
3796 * @since 2.0.0
3797 *
3798 * @param WP_Query $query The WP_Query instance (passed by reference).
3799 */
3800 do_action_ref_array( 'loop_start', array( &$this ) );
3801 }
3802
3803 $post = $this->next_post();
3804
3805 // Ensure a full post object is available.
3806 if ( 'all' !== $this->query_vars['fields'] ) {
3807 if ( 'ids' === $this->query_vars['fields'] ) {
3808 // Post IDs queried.
3809 $post = get_post( $post );
3810 } elseif ( isset( $post->ID ) ) {
3811 /*
3812 * Partial objecct queried.
3813 *
3814 * The post object was queried with a partial set of
3815 * fields, populate the entire object for the loop.
3816 */
3817 $post = get_post( $post->ID );
3818 }
3819 }
3820
3821 // Set up the global post object for the loop.
3822 $this->setup_postdata( $post );
3823 }
3824
3825 /**
3826 * Determines whether there are more posts available in the loop.
3827 *
3828 * Calls the {@see 'loop_end'} action when the loop is complete.
3829 *
3830 * @since 1.5.0
3831 *
3832 * @return bool True if posts are available, false if end of the loop.
3833 */
3834 public function have_posts() {
3835 if ( $this->current_post + 1 < $this->post_count ) {
3836 return true;
3837 } elseif ( $this->current_post + 1 === $this->post_count && $this->post_count > 0 ) {
3838 /**
3839 * Fires once the loop has ended.
3840 *
3841 * @since 2.0.0
3842 *
3843 * @param WP_Query $query The WP_Query instance (passed by reference).
3844 */
3845 do_action_ref_array( 'loop_end', array( &$this ) );
3846
3847 // Do some cleaning up after the loop.
3848 $this->rewind_posts();
3849 } elseif ( 0 === $this->post_count ) {
3850 $this->before_loop = false;
3851
3852 /**
3853 * Fires if no results are found in a post query.
3854 *
3855 * @since 4.9.0
3856 *
3857 * @param WP_Query $query The WP_Query instance.
3858 */
3859 do_action( 'loop_no_results', $this );
3860 }
3861
3862 $this->in_the_loop = false;
3863 return false;
3864 }
3865
3866 /**
3867 * Rewinds the posts and resets post index.
3868 *
3869 * @since 1.5.0
3870 */
3871 public function rewind_posts() {
3872 $this->current_post = -1;
3873 if ( $this->post_count > 0 ) {
3874 $this->post = $this->posts[0];
3875 }
3876 }
3877
3878 /**
3879 * Iterates current comment index and returns WP_Comment object.
3880 *
3881 * @since 2.2.0
3882 *
3883 * @return WP_Comment Comment object.
3884 */
3885 public function next_comment() {
3886 ++$this->current_comment;
3887
3888 /** @var WP_Comment */
3889 $this->comment = $this->comments[ $this->current_comment ];
3890 return $this->comment;
3891 }
3892
3893 /**
3894 * Sets up the current comment.
3895 *
3896 * @since 2.2.0
3897 *
3898 * @global WP_Comment $comment Global comment object.
3899 */
3900 public function the_comment() {
3901 global $comment;
3902
3903 $comment = $this->next_comment();
3904
3905 if ( 0 === $this->current_comment ) {
3906 /**
3907 * Fires once the comment loop is started.
3908 *
3909 * @since 2.2.0
3910 */
3911 do_action( 'comment_loop_start' );
3912 }
3913 }
3914
3915 /**
3916 * Determines whether there are more comments available.
3917 *
3918 * Automatically rewinds comments when finished.
3919 *
3920 * @since 2.2.0
3921 *
3922 * @return bool True if comments are available, false if no more comments.
3923 */
3924 public function have_comments() {
3925 if ( $this->current_comment + 1 < $this->comment_count ) {
3926 return true;
3927 } elseif ( $this->current_comment + 1 === $this->comment_count ) {
3928 $this->rewind_comments();
3929 }
3930
3931 return false;
3932 }
3933
3934 /**
3935 * Rewinds the comments, resets the comment index and comment to first.
3936 *
3937 * @since 2.2.0
3938 */
3939 public function rewind_comments() {
3940 $this->current_comment = -1;
3941 if ( $this->comment_count > 0 ) {
3942 $this->comment = $this->comments[0];
3943 }
3944 }
3945
3946 /**
3947 * Sets up the WordPress query by parsing query string.
3948 *
3949 * @since 1.5.0
3950 *
3951 * @see WP_Query::parse_query() for all available arguments.
3952 *
3953 * @param string|array $query URL query string or array of query arguments.
3954 * @return WP_Post[]|int[] Array of post objects or post IDs.
3955 */
3956 public function query( $query ) {
3957 $this->init();
3958 $this->query = wp_parse_args( $query );
3959 $this->query_vars = $this->query;
3960 return $this->get_posts();
3961 }
3962
3963 /**
3964 * Retrieves the currently queried object.
3965 *
3966 * If queried object is not set, then the queried object will be set from
3967 * the category, tag, taxonomy, posts page, single post, page, or author
3968 * query variable. After it is set up, it will be returned.
3969 *
3970 * @since 1.5.0
3971 *
3972 * @return WP_Term|WP_Post_Type|WP_Post|WP_User|null The queried object.
3973 */
3974 public function get_queried_object() {
3975 if ( isset( $this->queried_object ) ) {
3976 return $this->queried_object;
3977 }
3978
3979 $this->queried_object = null;
3980 $this->queried_object_id = null;
3981
3982 if ( $this->is_category || $this->is_tag || $this->is_tax ) {
3983 if ( $this->is_category ) {
3984 $cat = $this->get( 'cat' );
3985 $category_name = $this->get( 'category_name' );
3986
3987 if ( $cat ) {
3988 $term = get_term( $cat, 'category' );
3989 } elseif ( $category_name ) {
3990 $term = get_term_by( 'slug', $category_name, 'category' );
3991 }
3992 } elseif ( $this->is_tag ) {
3993 $tag_id = $this->get( 'tag_id' );
3994 $tag = $this->get( 'tag' );
3995
3996 if ( $tag_id ) {
3997 $term = get_term( $tag_id, 'post_tag' );
3998 } elseif ( $tag ) {
3999 $term = get_term_by( 'slug', $tag, 'post_tag' );
4000 }
4001 } else {
4002 // For other tax queries, grab the first term from the first clause.
4003 if ( ! empty( $this->tax_query->queried_terms ) ) {
4004 $queried_taxonomies = array_keys( $this->tax_query->queried_terms );
4005 $matched_taxonomy = reset( $queried_taxonomies );
4006 $query = $this->tax_query->queried_terms[ $matched_taxonomy ];
4007
4008 if ( ! empty( $query['terms'] ) ) {
4009 if ( 'term_id' === $query['field'] ) {
4010 $term = get_term( reset( $query['terms'] ), $matched_taxonomy );
4011 } else {
4012 $term = get_term_by( $query['field'], reset( $query['terms'] ), $matched_taxonomy );
4013 }
4014 }
4015 }
4016 }
4017
4018 if ( ! empty( $term ) && ! is_wp_error( $term ) ) {
4019 $this->queried_object = $term;
4020 $this->queried_object_id = (int) $term->term_id;
4021
4022 if ( $this->is_category && 'category' === $this->queried_object->taxonomy ) {
4023 _make_cat_compat( $this->queried_object );
4024 }
4025 }
4026 } elseif ( $this->is_post_type_archive ) {
4027 $post_type = $this->get( 'post_type' );
4028
4029 if ( is_array( $post_type ) ) {
4030 $post_type = reset( $post_type );
4031 }
4032
4033 $this->queried_object = get_post_type_object( $post_type );
4034 } elseif ( $this->is_posts_page ) {
4035 $page_for_posts = get_option( 'page_for_posts' );
4036
4037 $this->queried_object = get_post( $page_for_posts );
4038 $this->queried_object_id = (int) $this->queried_object->ID;
4039 } elseif ( $this->is_singular && ! empty( $this->post ) ) {
4040 $this->queried_object = $this->post;
4041 $this->queried_object_id = (int) $this->post->ID;
4042 } elseif ( $this->is_author ) {
4043 $author = (int) $this->get( 'author' );
4044 $author_name = $this->get( 'author_name' );
4045
4046 if ( $author ) {
4047 $this->queried_object_id = $author;
4048 } elseif ( $author_name ) {
4049 $user = get_user_by( 'slug', $author_name );
4050
4051 if ( $user ) {
4052 $this->queried_object_id = $user->ID;
4053 }
4054 }
4055
4056 $this->queried_object = get_userdata( $this->queried_object_id );
4057 }
4058
4059 return $this->queried_object;
4060 }
4061
4062 /**
4063 * Retrieves the ID of the currently queried object.
4064 *
4065 * @since 1.5.0
4066 *
4067 * @return int
4068 */
4069 public function get_queried_object_id() {
4070 $this->get_queried_object();
4071
4072 if ( isset( $this->queried_object_id ) ) {
4073 return $this->queried_object_id;
4074 }
4075
4076 return 0;
4077 }
4078
4079 /**
4080 * Constructor.
4081 *
4082 * Sets up the WordPress query, if parameter is not empty.
4083 *
4084 * @since 1.5.0
4085 *
4086 * @see WP_Query::parse_query() for all available arguments.
4087 *
4088 * @param string|array $query URL query string or array of vars.
4089 */
4090 public function __construct( $query = '' ) {
4091 if ( ! empty( $query ) ) {
4092 $this->query( $query );
4093 }
4094 }
4095
4096 /**
4097 * Makes private properties readable for backward compatibility.
4098 *
4099 * @since 4.0.0
4100 *
4101 * @param string $name Property to get.
4102 * @return mixed Property.
4103 */
4104 public function __get( $name ) {
4105 if ( in_array( $name, $this->compat_fields, true ) ) {
4106 return $this->$name;
4107 }
4108 }
4109
4110 /**
4111 * Makes private properties checkable for backward compatibility.
4112 *
4113 * @since 4.0.0
4114 *
4115 * @param string $name Property to check if set.
4116 * @return bool Whether the property is set.
4117 */
4118 public function __isset( $name ) {
4119 if ( in_array( $name, $this->compat_fields, true ) ) {
4120 return isset( $this->$name );
4121 }
4122
4123 return false;
4124 }
4125
4126 /**
4127 * Makes private/protected methods readable for backward compatibility.
4128 *
4129 * @since 4.0.0
4130 *
4131 * @param string $name Method to call.
4132 * @param array $arguments Arguments to pass when calling.
4133 * @return mixed|false Return value of the callback, false otherwise.
4134 */
4135 public function __call( $name, $arguments ) {
4136 if ( in_array( $name, $this->compat_methods, true ) ) {
4137 return $this->$name( ...$arguments );
4138 }
4139 return false;
4140 }
4141
4142 /**
4143 * Determines whether the query is for an existing archive page.
4144 *
4145 * Archive pages include category, tag, author, date, custom post type,
4146 * and custom taxonomy based archives.
4147 *
4148 * @since 3.1.0
4149 *
4150 * @see WP_Query::is_category()
4151 * @see WP_Query::is_tag()
4152 * @see WP_Query::is_author()
4153 * @see WP_Query::is_date()
4154 * @see WP_Query::is_post_type_archive()
4155 * @see WP_Query::is_tax()
4156 *
4157 * @return bool Whether the query is for an existing archive page.
4158 */
4159 public function is_archive() {
4160 return (bool) $this->is_archive;
4161 }
4162
4163 /**
4164 * Determines whether the query is for an existing post type archive page.
4165 *
4166 * @since 3.1.0
4167 *
4168 * @param string|string[] $post_types Optional. Post type or array of posts types
4169 * to check against. Default empty.
4170 * @return bool Whether the query is for an existing post type archive page.
4171 */
4172 public function is_post_type_archive( $post_types = '' ) {
4173 if ( empty( $post_types ) || ! $this->is_post_type_archive ) {
4174 return (bool) $this->is_post_type_archive;
4175 }
4176
4177 $post_type = $this->get( 'post_type' );
4178 if ( is_array( $post_type ) ) {
4179 $post_type = reset( $post_type );
4180 }
4181 $post_type_object = get_post_type_object( $post_type );
4182
4183 if ( ! $post_type_object ) {
4184 return false;
4185 }
4186
4187 return in_array( $post_type_object->name, (array) $post_types, true );
4188 }
4189
4190 /**
4191 * Determines whether the query is for an existing attachment page.
4192 *
4193 * @since 3.1.0
4194 *
4195 * @param int|string|int[]|string[] $attachment Optional. Attachment ID, title, slug, or array of such
4196 * to check against. Default empty.
4197 * @return bool Whether the query is for an existing attachment page.
4198 */
4199 public function is_attachment( $attachment = '' ) {
4200 if ( ! $this->is_attachment ) {
4201 return false;
4202 }
4203
4204 if ( empty( $attachment ) ) {
4205 return true;
4206 }
4207
4208 $attachment = array_map( 'strval', (array) $attachment );
4209
4210 $post_obj = $this->get_queried_object();
4211 if ( ! $post_obj ) {
4212 return false;
4213 }
4214
4215 if ( in_array( (string) $post_obj->ID, $attachment, true ) ) {
4216 return true;
4217 } elseif ( in_array( $post_obj->post_title, $attachment, true ) ) {
4218 return true;
4219 } elseif ( in_array( $post_obj->post_name, $attachment, true ) ) {
4220 return true;
4221 }
4222 return false;
4223 }
4224
4225 /**
4226 * Determines whether the query is for an existing author archive page.
4227 *
4228 * If the $author parameter is specified, this function will additionally
4229 * check if the query is for one of the authors specified.
4230 *
4231 * @since 3.1.0
4232 *
4233 * @param int|string|int[]|string[] $author Optional. User ID, nickname, nicename, or array of such
4234 * to check against. Default empty.
4235 * @return bool Whether the query is for an existing author archive page.
4236 */
4237 public function is_author( $author = '' ) {
4238 if ( ! $this->is_author ) {
4239 return false;
4240 }
4241
4242 if ( empty( $author ) ) {
4243 return true;
4244 }
4245
4246 $author_obj = $this->get_queried_object();
4247 if ( ! $author_obj ) {
4248 return false;
4249 }
4250
4251 $author = array_map( 'strval', (array) $author );
4252
4253 if ( in_array( (string) $author_obj->ID, $author, true ) ) {
4254 return true;
4255 } elseif ( in_array( $author_obj->nickname, $author, true ) ) {
4256 return true;
4257 } elseif ( in_array( $author_obj->user_nicename, $author, true ) ) {
4258 return true;
4259 }
4260
4261 return false;
4262 }
4263
4264 /**
4265 * Determines whether the query is for an existing category archive page.
4266 *
4267 * If the $category parameter is specified, this function will additionally
4268 * check if the query is for one of the categories specified.
4269 *
4270 * @since 3.1.0
4271 *
4272 * @param int|string|int[]|string[] $category Optional. Category ID, name, slug, or array of such
4273 * to check against. Default empty.
4274 * @return bool Whether the query is for an existing category archive page.
4275 */
4276 public function is_category( $category = '' ) {
4277 if ( ! $this->is_category ) {
4278 return false;
4279 }
4280
4281 if ( empty( $category ) ) {
4282 return true;
4283 }
4284
4285 $cat_obj = $this->get_queried_object();
4286 if ( ! $cat_obj ) {
4287 return false;
4288 }
4289
4290 $category = array_map( 'strval', (array) $category );
4291
4292 if ( in_array( (string) $cat_obj->term_id, $category, true ) ) {
4293 return true;
4294 } elseif ( in_array( $cat_obj->name, $category, true ) ) {
4295 return true;
4296 } elseif ( in_array( $cat_obj->slug, $category, true ) ) {
4297 return true;
4298 }
4299
4300 return false;
4301 }
4302
4303 /**
4304 * Determines whether the query is for an existing tag archive page.
4305 *
4306 * If the $tag parameter is specified, this function will additionally
4307 * check if the query is for one of the tags specified.
4308 *
4309 * @since 3.1.0
4310 *
4311 * @param int|string|int[]|string[] $tag Optional. Tag ID, name, slug, or array of such
4312 * to check against. Default empty.
4313 * @return bool Whether the query is for an existing tag archive page.
4314 */
4315 public function is_tag( $tag = '' ) {
4316 if ( ! $this->is_tag ) {
4317 return false;
4318 }
4319
4320 if ( empty( $tag ) ) {
4321 return true;
4322 }
4323
4324 $tag_obj = $this->get_queried_object();
4325 if ( ! $tag_obj ) {
4326 return false;
4327 }
4328
4329 $tag = array_map( 'strval', (array) $tag );
4330
4331 if ( in_array( (string) $tag_obj->term_id, $tag, true ) ) {
4332 return true;
4333 } elseif ( in_array( $tag_obj->name, $tag, true ) ) {
4334 return true;
4335 } elseif ( in_array( $tag_obj->slug, $tag, true ) ) {
4336 return true;
4337 }
4338
4339 return false;
4340 }
4341
4342 /**
4343 * Determines whether the query is for an existing custom taxonomy archive page.
4344 *
4345 * If the $taxonomy parameter is specified, this function will additionally
4346 * check if the query is for that specific $taxonomy.
4347 *
4348 * If the $term parameter is specified in addition to the $taxonomy parameter,
4349 * this function will additionally check if the query is for one of the terms
4350 * specified.
4351 *
4352 * @since 3.1.0
4353 *
4354 * @global WP_Taxonomy[] $wp_taxonomies Registered taxonomies.
4355 *
4356 * @param string|string[] $taxonomy Optional. Taxonomy slug or slugs to check against.
4357 * Default empty.
4358 * @param int|string|int[]|string[] $term Optional. Term ID, name, slug, or array of such
4359 * to check against. Default empty.
4360 * @return bool Whether the query is for an existing custom taxonomy archive page.
4361 * True for custom taxonomy archive pages, false for built-in taxonomies
4362 * (category and tag archives).
4363 */
4364 public function is_tax( $taxonomy = '', $term = '' ) {
4365 global $wp_taxonomies;
4366
4367 if ( ! $this->is_tax ) {
4368 return false;
4369 }
4370
4371 if ( empty( $taxonomy ) ) {
4372 return true;
4373 }
4374
4375 $queried_object = $this->get_queried_object();
4376 $tax_array = array_intersect( array_keys( $wp_taxonomies ), (array) $taxonomy );
4377 $term_array = (array) $term;
4378
4379 // Check that the taxonomy matches.
4380 if ( ! ( isset( $queried_object->taxonomy ) && count( $tax_array ) && in_array( $queried_object->taxonomy, $tax_array, true ) ) ) {
4381 return false;
4382 }
4383
4384 // Only a taxonomy provided.
4385 if ( empty( $term ) ) {
4386 return true;
4387 }
4388
4389 return isset( $queried_object->term_id ) &&
4390 count(
4391 array_intersect(
4392 array( $queried_object->term_id, $queried_object->name, $queried_object->slug ),
4393 $term_array
4394 )
4395 );
4396 }
4397
4398 /**
4399 * Determines whether the current URL is within the comments popup window.
4400 *
4401 * @since 3.1.0
4402 * @deprecated 4.5.0
4403 *
4404 * @return false Always returns false.
4405 */
4406 public function is_comments_popup() {
4407 _deprecated_function( __FUNCTION__, '4.5.0' );
4408
4409 return false;
4410 }
4411
4412 /**
4413 * Determines whether the query is for an existing date archive.
4414 *
4415 * @since 3.1.0
4416 *
4417 * @return bool Whether the query is for an existing date archive.
4418 */
4419 public function is_date() {
4420 return (bool) $this->is_date;
4421 }
4422
4423 /**
4424 * Determines whether the query is for an existing day archive.
4425 *
4426 * @since 3.1.0
4427 *
4428 * @return bool Whether the query is for an existing day archive.
4429 */
4430 public function is_day() {
4431 return (bool) $this->is_day;
4432 }
4433
4434 /**
4435 * Determines whether the query is for a feed.
4436 *
4437 * @since 3.1.0
4438 *
4439 * @param string|string[] $feeds Optional. Feed type or array of feed types
4440 * to check against. Default empty.
4441 * @return bool Whether the query is for a feed.
4442 */
4443 public function is_feed( $feeds = '' ) {
4444 if ( empty( $feeds ) || ! $this->is_feed ) {
4445 return (bool) $this->is_feed;
4446 }
4447
4448 $query_var = $this->get( 'feed' );
4449 if ( 'feed' === $query_var ) {
4450 $query_var = get_default_feed();
4451 }
4452
4453 return in_array( $query_var, (array) $feeds, true );
4454 }
4455
4456 /**
4457 * Determines whether the query is for a comments feed.
4458 *
4459 * @since 3.1.0
4460 *
4461 * @return bool Whether the query is for a comments feed.
4462 */
4463 public function is_comment_feed() {
4464 return (bool) $this->is_comment_feed;
4465 }
4466
4467 /**
4468 * Determines whether the query is for the front page of the site.
4469 *
4470 * This is for what is displayed at your site's main URL.
4471 *
4472 * Depends on the site's "Front page displays" Reading Settings 'show_on_front' and 'page_on_front'.
4473 *
4474 * If you set a static page for the front page of your site, this function will return
4475 * true when viewing that page.
4476 *
4477 * Otherwise the same as {@see WP_Query::is_home()}.
4478 *
4479 * @since 3.1.0
4480 *
4481 * @return bool Whether the query is for the front page of the site.
4482 */
4483 public function is_front_page() {
4484 // Most likely case.
4485 if ( 'posts' === get_option( 'show_on_front' ) && $this->is_home() ) {
4486 return true;
4487 } elseif ( 'page' === get_option( 'show_on_front' ) && get_option( 'page_on_front' )
4488 && $this->is_page( get_option( 'page_on_front' ) )
4489 ) {
4490 return true;
4491 } else {
4492 return false;
4493 }
4494 }
4495
4496 /**
4497 * Determines whether the query is for the blog homepage.
4498 *
4499 * This is the page which shows the time based blog content of your site.
4500 *
4501 * Depends on the site's "Front page displays" Reading Settings 'show_on_front' and 'page_for_posts'.
4502 *
4503 * If you set a static page for the front page of your site, this function will return
4504 * true only on the page you set as the "Posts page".
4505 *
4506 * @since 3.1.0
4507 *
4508 * @see WP_Query::is_front_page()
4509 *
4510 * @return bool Whether the query is for the blog homepage.
4511 */
4512 public function is_home() {
4513 return (bool) $this->is_home;
4514 }
4515
4516 /**
4517 * Determines whether the query is for the Privacy Policy page.
4518 *
4519 * This is the page which shows the Privacy Policy content of your site.
4520 *
4521 * Depends on the site's "Change your Privacy Policy page" Privacy Settings 'wp_page_for_privacy_policy'.
4522 *
4523 * This function will return true only on the page you set as the "Privacy Policy page".
4524 *
4525 * @since 5.2.0
4526 *
4527 * @return bool Whether the query is for the Privacy Policy page.
4528 */
4529 public function is_privacy_policy() {
4530 if ( get_option( 'wp_page_for_privacy_policy' )
4531 && $this->is_page( get_option( 'wp_page_for_privacy_policy' ) )
4532 ) {
4533 return true;
4534 } else {
4535 return false;
4536 }
4537 }
4538
4539 /**
4540 * Determines whether the query is for an existing month archive.
4541 *
4542 * @since 3.1.0
4543 *
4544 * @return bool Whether the query is for an existing month archive.
4545 */
4546 public function is_month() {
4547 return (bool) $this->is_month;
4548 }
4549
4550 /**
4551 * Determines whether the query is for an existing single page.
4552 *
4553 * If the $page parameter is specified, this function will additionally
4554 * check if the query is for one of the pages specified.
4555 *
4556 * @since 3.1.0
4557 *
4558 * @see WP_Query::is_single()
4559 * @see WP_Query::is_singular()
4560 *
4561 * @param int|string|int[]|string[] $page Optional. Page ID, title, slug, path, or array of such
4562 * to check against. Default empty.
4563 * @return bool Whether the query is for an existing single page.
4564 */
4565 public function is_page( $page = '' ) {
4566 if ( ! $this->is_page ) {
4567 return false;
4568 }
4569
4570 if ( empty( $page ) ) {
4571 return true;
4572 }
4573
4574 $page_obj = $this->get_queried_object();
4575 if ( ! $page_obj ) {
4576 return false;
4577 }
4578
4579 $page = array_map( 'strval', (array) $page );
4580
4581 if ( in_array( (string) $page_obj->ID, $page, true ) ) {
4582 return true;
4583 } elseif ( in_array( $page_obj->post_title, $page, true ) ) {
4584 return true;
4585 } elseif ( in_array( $page_obj->post_name, $page, true ) ) {
4586 return true;
4587 } else {
4588 foreach ( $page as $pagepath ) {
4589 if ( ! strpos( $pagepath, '/' ) ) {
4590 continue;
4591 }
4592
4593 $pagepath_obj = get_page_by_path( $pagepath );
4594
4595 if ( $pagepath_obj && ( $pagepath_obj->ID === $page_obj->ID ) ) {
4596 return true;
4597 }
4598 }
4599 }
4600
4601 return false;
4602 }
4603
4604 /**
4605 * Determines whether the query is for a paged result and not for the first page.
4606 *
4607 * @since 3.1.0
4608 *
4609 * @return bool Whether the query is for a paged result.
4610 */
4611 public function is_paged() {
4612 return (bool) $this->is_paged;
4613 }
4614
4615 /**
4616 * Determines whether the query is for a post or page preview.
4617 *
4618 * @since 3.1.0
4619 *
4620 * @return bool Whether the query is for a post or page preview.
4621 */
4622 public function is_preview() {
4623 return (bool) $this->is_preview;
4624 }
4625
4626 /**
4627 * Determines whether the query is for the robots.txt file.
4628 *
4629 * @since 3.1.0
4630 *
4631 * @return bool Whether the query is for the robots.txt file.
4632 */
4633 public function is_robots() {
4634 return (bool) $this->is_robots;
4635 }
4636
4637 /**
4638 * Determines whether the query is for the favicon.ico file.
4639 *
4640 * @since 5.4.0
4641 *
4642 * @return bool Whether the query is for the favicon.ico file.
4643 */
4644 public function is_favicon() {
4645 return (bool) $this->is_favicon;
4646 }
4647
4648 /**
4649 * Determines whether the query is for a search.
4650 *
4651 * @since 3.1.0
4652 *
4653 * @return bool Whether the query is for a search.
4654 */
4655 public function is_search() {
4656 return (bool) $this->is_search;
4657 }
4658
4659 /**
4660 * Determines whether the query is for an existing single post.
4661 *
4662 * Works for any post type excluding pages.
4663 *
4664 * If the $post parameter is specified, this function will additionally
4665 * check if the query is for one of the Posts specified.
4666 *
4667 * @since 3.1.0
4668 *
4669 * @see WP_Query::is_page()
4670 * @see WP_Query::is_singular()
4671 *
4672 * @param int|string|int[]|string[] $post Optional. Post ID, title, slug, path, or array of such
4673 * to check against. Default empty.
4674 * @return bool Whether the query is for an existing single post.
4675 */
4676 public function is_single( $post = '' ) {
4677 if ( ! $this->is_single ) {
4678 return false;
4679 }
4680
4681 if ( empty( $post ) ) {
4682 return true;
4683 }
4684
4685 $post_obj = $this->get_queried_object();
4686 if ( ! $post_obj ) {
4687 return false;
4688 }
4689
4690 $post = array_map( 'strval', (array) $post );
4691
4692 if ( in_array( (string) $post_obj->ID, $post, true ) ) {
4693 return true;
4694 } elseif ( in_array( $post_obj->post_title, $post, true ) ) {
4695 return true;
4696 } elseif ( in_array( $post_obj->post_name, $post, true ) ) {
4697 return true;
4698 } else {
4699 foreach ( $post as $postpath ) {
4700 if ( ! strpos( $postpath, '/' ) ) {
4701 continue;
4702 }
4703
4704 $postpath_obj = get_page_by_path( $postpath, OBJECT, $post_obj->post_type );
4705
4706 if ( $postpath_obj && ( $postpath_obj->ID === $post_obj->ID ) ) {
4707 return true;
4708 }
4709 }
4710 }
4711 return false;
4712 }
4713
4714 /**
4715 * Determines whether the query is for an existing single post of any post type
4716 * (post, attachment, page, custom post types).
4717 *
4718 * If the $post_types parameter is specified, this function will additionally
4719 * check if the query is for one of the Posts Types specified.
4720 *
4721 * @since 3.1.0
4722 *
4723 * @see WP_Query::is_page()
4724 * @see WP_Query::is_single()
4725 *
4726 * @param string|string[] $post_types Optional. Post type or array of post types
4727 * to check against. Default empty.
4728 * @return bool Whether the query is for an existing single post
4729 * or any of the given post types.
4730 */
4731 public function is_singular( $post_types = '' ) {
4732 if ( empty( $post_types ) || ! $this->is_singular ) {
4733 return (bool) $this->is_singular;
4734 }
4735
4736 $post_obj = $this->get_queried_object();
4737 if ( ! $post_obj ) {
4738 return false;
4739 }
4740
4741 return in_array( $post_obj->post_type, (array) $post_types, true );
4742 }
4743
4744 /**
4745 * Determines whether the query is for a specific time.
4746 *
4747 * @since 3.1.0
4748 *
4749 * @return bool Whether the query is for a specific time.
4750 */
4751 public function is_time() {
4752 return (bool) $this->is_time;
4753 }
4754
4755 /**
4756 * Determines whether the query is for a trackback endpoint call.
4757 *
4758 * @since 3.1.0
4759 *
4760 * @return bool Whether the query is for a trackback endpoint call.
4761 */
4762 public function is_trackback() {
4763 return (bool) $this->is_trackback;
4764 }
4765
4766 /**
4767 * Determines whether the query is for an existing year archive.
4768 *
4769 * @since 3.1.0
4770 *
4771 * @return bool Whether the query is for an existing year archive.
4772 */
4773 public function is_year() {
4774 return (bool) $this->is_year;
4775 }
4776
4777 /**
4778 * Determines whether the query is a 404 (returns no results).
4779 *
4780 * @since 3.1.0
4781 *
4782 * @return bool Whether the query is a 404 error.
4783 */
4784 public function is_404() {
4785 return (bool) $this->is_404;
4786 }
4787
4788 /**
4789 * Determines whether the query is for an embedded post.
4790 *
4791 * @since 4.4.0
4792 *
4793 * @return bool Whether the query is for an embedded post.
4794 */
4795 public function is_embed() {
4796 return (bool) $this->is_embed;
4797 }
4798
4799 /**
4800 * Determines whether the query is the main query.
4801 *
4802 * @since 3.3.0
4803 *
4804 * @global WP_Query $wp_the_query WordPress Query object.
4805 *
4806 * @return bool Whether the query is the main query.
4807 */
4808 public function is_main_query() {
4809 global $wp_the_query;
4810 return $wp_the_query === $this;
4811 }
4812
4813 /**
4814 * Sets up global post data.
4815 *
4816 * @since 4.1.0
4817 * @since 4.4.0 Added the ability to pass a post ID to `$post`.
4818 *
4819 * @global int $id
4820 * @global WP_User $authordata
4821 * @global string $currentday
4822 * @global string $currentmonth
4823 * @global int $page
4824 * @global array $pages
4825 * @global int $multipage
4826 * @global int $more
4827 * @global int $numpages
4828 *
4829 * @param WP_Post|object|int $post WP_Post instance or Post ID/object.
4830 * @return true True when finished.
4831 */
4832 public function setup_postdata( $post ) {
4833 global $id, $authordata, $currentday, $currentmonth, $page, $pages, $multipage, $more, $numpages;
4834
4835 if ( ! ( $post instanceof WP_Post ) ) {
4836 $post = get_post( $post );
4837 }
4838
4839 if ( ! $post ) {
4840 return;
4841 }
4842
4843 $elements = $this->generate_postdata( $post );
4844 if ( false === $elements ) {
4845 return;
4846 }
4847
4848 $id = $elements['id'];
4849 $authordata = $elements['authordata'];
4850 $currentday = $elements['currentday'];
4851 $currentmonth = $elements['currentmonth'];
4852 $page = $elements['page'];
4853 $pages = $elements['pages'];
4854 $multipage = $elements['multipage'];
4855 $more = $elements['more'];
4856 $numpages = $elements['numpages'];
4857
4858 /**
4859 * Fires once the post data has been set up.
4860 *
4861 * @since 2.8.0
4862 * @since 4.1.0 Introduced `$query` parameter.
4863 *
4864 * @param WP_Post $post The Post object (passed by reference).
4865 * @param WP_Query $query The current Query object (passed by reference).
4866 */
4867 do_action_ref_array( 'the_post', array( &$post, &$this ) );
4868
4869 return true;
4870 }
4871
4872 /**
4873 * Generates post data.
4874 *
4875 * @since 5.2.0
4876 *
4877 * @param WP_Post|object|int $post WP_Post instance or Post ID/object.
4878 * @return array|false Elements of post or false on failure.
4879 */
4880 public function generate_postdata( $post ) {
4881
4882 if ( ! ( $post instanceof WP_Post ) ) {
4883 $post = get_post( $post );
4884 }
4885
4886 if ( ! $post ) {
4887 return false;
4888 }
4889
4890 $id = (int) $post->ID;
4891
4892 $authordata = get_userdata( $post->post_author );
4893
4894 $currentday = false;
4895 $currentmonth = false;
4896
4897 $post_date = $post->post_date;
4898 if ( ! empty( $post_date ) && '0000-00-00 00:00:00' !== $post_date ) {
4899 // Avoid using mysql2date for performance reasons.
4900 $currentmonth = substr( $post_date, 5, 2 );
4901 $day = substr( $post_date, 8, 2 );
4902 $year = substr( $post_date, 2, 2 );
4903
4904 $currentday = sprintf( '%s.%s.%s', $day, $currentmonth, $year );
4905 }
4906
4907 $numpages = 1;
4908 $multipage = 0;
4909 $page = $this->get( 'page' );
4910 if ( ! $page ) {
4911 $page = 1;
4912 }
4913
4914 /*
4915 * Force full post content when viewing the permalink for the $post,
4916 * or when on an RSS feed. Otherwise respect the 'more' tag.
4917 */
4918 if ( get_queried_object_id() === $post->ID && ( $this->is_page() || $this->is_single() ) ) {
4919 $more = 1;
4920 } elseif ( $this->is_feed() ) {
4921 $more = 1;
4922 } else {
4923 $more = 0;
4924 }
4925
4926 $content = $post->post_content;
4927 if ( str_contains( $content, '<!--nextpage-->' ) ) {
4928 $content = str_replace( "\n<!--nextpage-->\n", '<!--nextpage-->', $content );
4929 $content = str_replace( "\n<!--nextpage-->", '<!--nextpage-->', $content );
4930 $content = str_replace( "<!--nextpage-->\n", '<!--nextpage-->', $content );
4931
4932 // Remove the nextpage block delimiters, to avoid invalid block structures in the split content.
4933 $content = str_replace( '<!-- wp:nextpage -->', '', $content );
4934 $content = str_replace( '<!-- /wp:nextpage -->', '', $content );
4935
4936 // Ignore nextpage at the beginning of the content.
4937 if ( str_starts_with( $content, '<!--nextpage-->' ) ) {
4938 $content = substr( $content, 15 );
4939 }
4940
4941 $pages = explode( '<!--nextpage-->', $content );
4942 } else {
4943 $pages = array( $post->post_content );
4944 }
4945
4946 /**
4947 * Filters the "pages" derived from splitting the post content.
4948 *
4949 * "Pages" are determined by splitting the post content based on the presence
4950 * of `<!-- nextpage -->` tags.
4951 *
4952 * @since 4.4.0
4953 *
4954 * @param string[] $pages Array of "pages" from the post content split by `<!-- nextpage -->` tags.
4955 * @param WP_Post $post Current post object.
4956 */
4957 $pages = apply_filters( 'content_pagination', $pages, $post );
4958
4959 $numpages = count( $pages );
4960
4961 if ( $numpages > 1 ) {
4962 if ( $page > 1 ) {
4963 $more = 1;
4964 }
4965 $multipage = 1;
4966 } else {
4967 $multipage = 0;
4968 }
4969
4970 $elements = compact( 'id', 'authordata', 'currentday', 'currentmonth', 'page', 'pages', 'multipage', 'more', 'numpages' );
4971
4972 return $elements;
4973 }
4974
4975 /**
4976 * Generates cache key.
4977 *
4978 * @since 6.1.0
4979 *
4980 * @global wpdb $wpdb WordPress database abstraction object.
4981 *
4982 * @param array $args Query arguments.
4983 * @param string $sql SQL statement.
4984 * @return string Cache key.
4985 */
4986 protected function generate_cache_key( array $args, $sql ) {
4987 global $wpdb;
4988
4989 unset(
4990 $args['cache_results'],
4991 $args['fields'],
4992 $args['lazy_load_term_meta'],
4993 $args['update_post_meta_cache'],
4994 $args['update_post_term_cache'],
4995 $args['update_menu_item_cache'],
4996 $args['suppress_filters']
4997 );
4998
4999 if ( empty( $args['post_type'] ) ) {
5000 if ( $this->is_attachment ) {
5001 $args['post_type'] = 'attachment';
5002 } elseif ( $this->is_page ) {
5003 $args['post_type'] = 'page';
5004 } else {
5005 $args['post_type'] = 'post';
5006 }
5007 } elseif ( 'any' === $args['post_type'] ) {
5008 $args['post_type'] = array_values( get_post_types( array( 'exclude_from_search' => false ) ) );
5009 }
5010 $args['post_type'] = (array) $args['post_type'];
5011 // Sort post types to ensure same cache key generation.
5012 sort( $args['post_type'] );
5013
5014 /*
5015 * Sort arrays that can be used for ordering prior to cache key generation.
5016 *
5017 * These arrays are sorted in the query generator for the purposes of the
5018 * WHERE clause but the arguments are not modified as they can be used for
5019 * the orderby clause.
5020 *
5021 * Their use in the orderby clause will generate a different SQL query so
5022 * they can be sorted for the cache key generation.
5023 */
5024 $sortable_arrays_with_int_values = array(
5025 'post__in',
5026 'post_parent__in',
5027 );
5028 foreach ( $sortable_arrays_with_int_values as $key ) {
5029 if ( isset( $args[ $key ] ) && is_array( $args[ $key ] ) ) {
5030 $args[ $key ] = array_unique( array_map( 'absint', $args[ $key ] ) );
5031 sort( $args[ $key ] );
5032 }
5033 }
5034
5035 // Sort and unique the 'post_name__in' for cache key generation.
5036 if ( isset( $args['post_name__in'] ) && is_array( $args['post_name__in'] ) ) {
5037 $args['post_name__in'] = array_unique( $args['post_name__in'] );
5038 sort( $args['post_name__in'] );
5039 }
5040
5041 if ( isset( $args['post_status'] ) ) {
5042 $args['post_status'] = (array) $args['post_status'];
5043 // Sort post status to ensure same cache key generation.
5044 sort( $args['post_status'] );
5045 }
5046
5047 // Add a default orderby value of date to ensure same cache key generation.
5048 if ( ! isset( $args['orderby'] ) ) {
5049 $args['orderby'] = 'date';
5050 }
5051
5052 $placeholder = $wpdb->placeholder_escape();
5053 array_walk_recursive(
5054 $args,
5055 /*
5056 * Replace wpdb placeholders with the string used in the database
5057 * query to avoid unreachable cache keys. This is necessary because
5058 * the placeholder is randomly generated in each request.
5059 *
5060 * $value is passed by reference to allow it to be modified.
5061 * array_walk_recursive() does not return an array.
5062 */
5063 static function ( &$value ) use ( $wpdb, $placeholder ) {
5064 if ( is_string( $value ) && str_contains( $value, $placeholder ) ) {
5065 $value = $wpdb->remove_placeholder_escape( $value );
5066 }
5067 }
5068 );
5069
5070 ksort( $args );
5071
5072 // Replace wpdb placeholder in the SQL statement used by the cache key.
5073 $sql = $wpdb->remove_placeholder_escape( $sql );
5074 $key = md5( serialize( $args ) . $sql );
5075
5076 $this->query_cache_key = "wp_query:$key";
5077 return $this->query_cache_key;
5078 }
5079
5080 /**
5081 * After looping through a nested query, this function
5082 * restores the $post global to the current post in this query.
5083 *
5084 * @since 3.7.0
5085 *
5086 * @global WP_Post $post Global post object.
5087 */
5088 public function reset_postdata() {
5089 if ( ! empty( $this->post ) ) {
5090 $GLOBALS['post'] = $this->post;
5091 $this->setup_postdata( $this->post );
5092 }
5093 }
5094
5095 /**
5096 * Lazyloads term meta for posts in the loop.
5097 *
5098 * @since 4.4.0
5099 * @deprecated 4.5.0 See wp_queue_posts_for_term_meta_lazyload().
5100 *
5101 * @param mixed $check
5102 * @param int $term_id
5103 * @return mixed
5104 */
5105 public function lazyload_term_meta( $check, $term_id ) {
5106 _deprecated_function( __METHOD__, '4.5.0' );
5107 return $check;
5108 }
5109
5110 /**
5111 * Lazyloads comment meta for comments in the loop.
5112 *
5113 * @since 4.4.0
5114 * @deprecated 4.5.0 See wp_lazyload_comment_meta().
5115 *
5116 * @param mixed $check
5117 * @param int $comment_id
5118 * @return mixed
5119 */
5120 public function lazyload_comment_meta( $check, $comment_id ) {
5121 _deprecated_function( __METHOD__, '4.5.0' );
5122 return $check;
5123 }
5124}
5125