Make WordPress Core

Ticket #38709: class-wp-query.php

File class-wp-query.php, 148.1 KB (added by Khadreal, 3 years ago)

The patch suggested by @rebasaurus remove the query but doesn't resolve the issue as draft post still shows up. Forcing a limit when it's singular and sanitize the input kind of do the trick and resolve it.

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