Make WordPress Core

Ticket #60490: class-wp-query.php

File class-wp-query.php, 148.3 KB (added by bburgay, 2 years ago)

Modified to add authorin orderby option

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 || ( defined( 'REST_REQUEST' ) && 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 'author__in'
1686 );
1687
1688 $primary_meta_key = '';
1689 $primary_meta_query = false;
1690 $meta_clauses = $this->meta_query->get_clauses();
1691 if ( ! empty( $meta_clauses ) ) {
1692 $primary_meta_query = reset( $meta_clauses );
1693
1694 if ( ! empty( $primary_meta_query['key'] ) ) {
1695 $primary_meta_key = $primary_meta_query['key'];
1696 $allowed_keys[] = $primary_meta_key;
1697 }
1698
1699 $allowed_keys[] = 'meta_value';
1700 $allowed_keys[] = 'meta_value_num';
1701 $allowed_keys = array_merge( $allowed_keys, array_keys( $meta_clauses ) );
1702 }
1703
1704 // If RAND() contains a seed value, sanitize and add to allowed keys.
1705 $rand_with_seed = false;
1706 if ( preg_match( '/RAND\(([0-9]+)\)/i', $orderby, $matches ) ) {
1707 $orderby = sprintf( 'RAND(%s)', (int) $matches[1] );
1708 $allowed_keys[] = $orderby;
1709 $rand_with_seed = true;
1710 }
1711
1712 if ( ! in_array( $orderby, $allowed_keys, true ) ) {
1713 return false;
1714 }
1715
1716 $orderby_clause = '';
1717
1718 switch ( $orderby ) {
1719 case 'post_name':
1720 case 'post_author':
1721 case 'post_date':
1722 case 'post_title':
1723 case 'post_modified':
1724 case 'post_parent':
1725 case 'post_type':
1726 case 'ID':
1727 case 'menu_order':
1728 case 'comment_count':
1729 $orderby_clause = "{$wpdb->posts}.{$orderby}";
1730 break;
1731 case 'rand':
1732 $orderby_clause = 'RAND()';
1733 break;
1734 case $primary_meta_key:
1735 case 'meta_value':
1736 if ( ! empty( $primary_meta_query['type'] ) ) {
1737 $orderby_clause = "CAST({$primary_meta_query['alias']}.meta_value AS {$primary_meta_query['cast']})";
1738 } else {
1739 $orderby_clause = "{$primary_meta_query['alias']}.meta_value";
1740 }
1741 break;
1742 case 'meta_value_num':
1743 $orderby_clause = "{$primary_meta_query['alias']}.meta_value+0";
1744 break;
1745 case 'post__in':
1746 if ( ! empty( $this->query_vars['post__in'] ) ) {
1747 $orderby_clause = "FIELD({$wpdb->posts}.ID," . implode( ',', array_map( 'absint', $this->query_vars['post__in'] ) ) . ')';
1748 }
1749 break;
1750 case 'post_parent__in':
1751 if ( ! empty( $this->query_vars['post_parent__in'] ) ) {
1752 $orderby_clause = "FIELD( {$wpdb->posts}.post_parent," . implode( ', ', array_map( 'absint', $this->query_vars['post_parent__in'] ) ) . ' )';
1753 }
1754 break;
1755 case 'post_name__in':
1756 if ( ! empty( $this->query_vars['post_name__in'] ) ) {
1757 $post_name__in = array_map( 'sanitize_title_for_query', $this->query_vars['post_name__in'] );
1758 $post_name__in_string = "'" . implode( "','", $post_name__in ) . "'";
1759 $orderby_clause = "FIELD( {$wpdb->posts}.post_name," . $post_name__in_string . ' )';
1760 }
1761 break;
1762 case 'author__in':
1763 if ( ! empty( $this->query_vars['author__in'] ) ) {
1764 $orderby_clause = "FIELD({$wpdb->posts}.post_author," . implode( ',', array_map( 'absint', $this->query_vars['author__in'] ) ) . ')';
1765 }
1766 break;
1767 default:
1768 if ( array_key_exists( $orderby, $meta_clauses ) ) {
1769 // $orderby corresponds to a meta_query clause.
1770 $meta_clause = $meta_clauses[ $orderby ];
1771 $orderby_clause = "CAST({$meta_clause['alias']}.meta_value AS {$meta_clause['cast']})";
1772 } elseif ( $rand_with_seed ) {
1773 $orderby_clause = $orderby;
1774 } else {
1775 // Default: order by post field.
1776 $orderby_clause = "{$wpdb->posts}.post_" . sanitize_key( $orderby );
1777 }
1778
1779 break;
1780 }
1781
1782 return $orderby_clause;
1783 }
1784
1785 /**
1786 * Parse an 'order' query variable and cast it to ASC or DESC as necessary.
1787 *
1788 * @since 4.0.0
1789 *
1790 * @param string $order The 'order' query variable.
1791 * @return string The sanitized 'order' query variable.
1792 */
1793 protected function parse_order( $order ) {
1794 if ( ! is_string( $order ) || empty( $order ) ) {
1795 return 'DESC';
1796 }
1797
1798 if ( 'ASC' === strtoupper( $order ) ) {
1799 return 'ASC';
1800 } else {
1801 return 'DESC';
1802 }
1803 }
1804
1805 /**
1806 * Sets the 404 property and saves whether query is feed.
1807 *
1808 * @since 2.0.0
1809 */
1810 public function set_404() {
1811 $is_feed = $this->is_feed;
1812
1813 $this->init_query_flags();
1814 $this->is_404 = true;
1815
1816 $this->is_feed = $is_feed;
1817
1818 /**
1819 * Fires after a 404 is triggered.
1820 *
1821 * @since 5.5.0
1822 *
1823 * @param WP_Query $query The WP_Query instance (passed by reference).
1824 */
1825 do_action_ref_array( 'set_404', array( $this ) );
1826 }
1827
1828 /**
1829 * Retrieves the value of a query variable.
1830 *
1831 * @since 1.5.0
1832 * @since 3.9.0 The `$default_value` argument was introduced.
1833 *
1834 * @param string $query_var Query variable key.
1835 * @param mixed $default_value Optional. Value to return if the query variable is not set.
1836 * Default empty string.
1837 * @return mixed Contents of the query variable.
1838 */
1839 public function get( $query_var, $default_value = '' ) {
1840 if ( isset( $this->query_vars[ $query_var ] ) ) {
1841 return $this->query_vars[ $query_var ];
1842 }
1843
1844 return $default_value;
1845 }
1846
1847 /**
1848 * Sets the value of a query variable.
1849 *
1850 * @since 1.5.0
1851 *
1852 * @param string $query_var Query variable key.
1853 * @param mixed $value Query variable value.
1854 */
1855 public function set( $query_var, $value ) {
1856 $this->query_vars[ $query_var ] = $value;
1857 }
1858
1859 /**
1860 * Retrieves an array of posts based on query variables.
1861 *
1862 * There are a few filters and actions that can be used to modify the post
1863 * database query.
1864 *
1865 * @since 1.5.0
1866 *
1867 * @global wpdb $wpdb WordPress database abstraction object.
1868 *
1869 * @return WP_Post[]|int[] Array of post objects or post IDs.
1870 */
1871 public function get_posts() {
1872 global $wpdb;
1873
1874 $this->parse_query();
1875
1876 /**
1877 * Fires after the query variable object is created, but before the actual query is run.
1878 *
1879 * Note: If using conditional tags, use the method versions within the passed instance
1880 * (e.g. $this->is_main_query() instead of is_main_query()). This is because the functions
1881 * like is_main_query() test against the global $wp_query instance, not the passed one.
1882 *
1883 * @since 2.0.0
1884 *
1885 * @param WP_Query $query The WP_Query instance (passed by reference).
1886 */
1887 do_action_ref_array( 'pre_get_posts', array( &$this ) );
1888
1889 // Shorthand.
1890 $q = &$this->query_vars;
1891
1892 // Fill again in case 'pre_get_posts' unset some vars.
1893 $q = $this->fill_query_vars( $q );
1894
1895 /**
1896 * Filters whether an attachment query should include filenames or not.
1897 *
1898 * @since 6.0.3
1899 *
1900 * @param bool $allow_query_attachment_by_filename Whether or not to include filenames.
1901 */
1902 $this->allow_query_attachment_by_filename = apply_filters( 'wp_allow_query_attachment_by_filename', false );
1903 remove_all_filters( 'wp_allow_query_attachment_by_filename' );
1904
1905 // Parse meta query.
1906 $this->meta_query = new WP_Meta_Query();
1907 $this->meta_query->parse_query_vars( $q );
1908
1909 // Set a flag if a 'pre_get_posts' hook changed the query vars.
1910 $hash = md5( serialize( $this->query_vars ) );
1911 if ( $hash != $this->query_vars_hash ) {
1912 $this->query_vars_changed = true;
1913 $this->query_vars_hash = $hash;
1914 }
1915 unset( $hash );
1916
1917 // First let's clear some variables.
1918 $distinct = '';
1919 $whichauthor = '';
1920 $whichmimetype = '';
1921 $where = '';
1922 $limits = '';
1923 $join = '';
1924 $search = '';
1925 $groupby = '';
1926 $post_status_join = false;
1927 $page = 1;
1928
1929 if ( isset( $q['caller_get_posts'] ) ) {
1930 _deprecated_argument(
1931 'WP_Query',
1932 '3.1.0',
1933 sprintf(
1934 /* translators: 1: caller_get_posts, 2: ignore_sticky_posts */
1935 __( '%1$s is deprecated. Use %2$s instead.' ),
1936 '<code>caller_get_posts</code>',
1937 '<code>ignore_sticky_posts</code>'
1938 )
1939 );
1940
1941 if ( ! isset( $q['ignore_sticky_posts'] ) ) {
1942 $q['ignore_sticky_posts'] = $q['caller_get_posts'];
1943 }
1944 }
1945
1946 if ( ! isset( $q['ignore_sticky_posts'] ) ) {
1947 $q['ignore_sticky_posts'] = false;
1948 }
1949
1950 if ( ! isset( $q['suppress_filters'] ) ) {
1951 $q['suppress_filters'] = false;
1952 }
1953
1954 if ( ! isset( $q['cache_results'] ) ) {
1955 $q['cache_results'] = true;
1956 }
1957
1958 if ( ! isset( $q['update_post_term_cache'] ) ) {
1959 $q['update_post_term_cache'] = true;
1960 }
1961
1962 if ( ! isset( $q['update_menu_item_cache'] ) ) {
1963 $q['update_menu_item_cache'] = false;
1964 }
1965
1966 if ( ! isset( $q['lazy_load_term_meta'] ) ) {
1967 $q['lazy_load_term_meta'] = $q['update_post_term_cache'];
1968 } elseif ( $q['lazy_load_term_meta'] ) { // Lazy loading term meta only works if term caches are primed.
1969 $q['update_post_term_cache'] = true;
1970 }
1971
1972 if ( ! isset( $q['update_post_meta_cache'] ) ) {
1973 $q['update_post_meta_cache'] = true;
1974 }
1975
1976 if ( ! isset( $q['post_type'] ) ) {
1977 if ( $this->is_search ) {
1978 $q['post_type'] = 'any';
1979 } else {
1980 $q['post_type'] = '';
1981 }
1982 }
1983 $post_type = $q['post_type'];
1984 if ( empty( $q['posts_per_page'] ) ) {
1985 $q['posts_per_page'] = get_option( 'posts_per_page' );
1986 }
1987 if ( isset( $q['showposts'] ) && $q['showposts'] ) {
1988 $q['showposts'] = (int) $q['showposts'];
1989 $q['posts_per_page'] = $q['showposts'];
1990 }
1991 if ( ( isset( $q['posts_per_archive_page'] ) && 0 != $q['posts_per_archive_page'] ) && ( $this->is_archive || $this->is_search ) ) {
1992 $q['posts_per_page'] = $q['posts_per_archive_page'];
1993 }
1994 if ( ! isset( $q['nopaging'] ) ) {
1995 if ( -1 == $q['posts_per_page'] ) {
1996 $q['nopaging'] = true;
1997 } else {
1998 $q['nopaging'] = false;
1999 }
2000 }
2001
2002 if ( $this->is_feed ) {
2003 // This overrides 'posts_per_page'.
2004 if ( ! empty( $q['posts_per_rss'] ) ) {
2005 $q['posts_per_page'] = $q['posts_per_rss'];
2006 } else {
2007 $q['posts_per_page'] = get_option( 'posts_per_rss' );
2008 }
2009 $q['nopaging'] = false;
2010 }
2011 $q['posts_per_page'] = (int) $q['posts_per_page'];
2012 if ( $q['posts_per_page'] < -1 ) {
2013 $q['posts_per_page'] = abs( $q['posts_per_page'] );
2014 } elseif ( 0 == $q['posts_per_page'] ) {
2015 $q['posts_per_page'] = 1;
2016 }
2017
2018 if ( ! isset( $q['comments_per_page'] ) || 0 == $q['comments_per_page'] ) {
2019 $q['comments_per_page'] = get_option( 'comments_per_page' );
2020 }
2021
2022 if ( $this->is_home && ( empty( $this->query ) || 'true' === $q['preview'] ) && ( 'page' === get_option( 'show_on_front' ) ) && get_option( 'page_on_front' ) ) {
2023 $this->is_page = true;
2024 $this->is_home = false;
2025 $q['page_id'] = get_option( 'page_on_front' );
2026 }
2027
2028 if ( isset( $q['page'] ) ) {
2029 $q['page'] = is_scalar( $q['page'] ) ? absint( trim( $q['page'], '/' ) ) : 0;
2030 }
2031
2032 // If true, forcibly turns off SQL_CALC_FOUND_ROWS even when limits are present.
2033 if ( isset( $q['no_found_rows'] ) ) {
2034 $q['no_found_rows'] = (bool) $q['no_found_rows'];
2035 } else {
2036 $q['no_found_rows'] = false;
2037 }
2038
2039 switch ( $q['fields'] ) {
2040 case 'ids':
2041 $fields = "{$wpdb->posts}.ID";
2042 break;
2043 case 'id=>parent':
2044 $fields = "{$wpdb->posts}.ID, {$wpdb->posts}.post_parent";
2045 break;
2046 default:
2047 $fields = "{$wpdb->posts}.*";
2048 }
2049
2050 if ( '' !== $q['menu_order'] ) {
2051 $where .= " AND {$wpdb->posts}.menu_order = " . $q['menu_order'];
2052 }
2053 // The "m" parameter is meant for months but accepts datetimes of varying specificity.
2054 if ( $q['m'] ) {
2055 $where .= " AND YEAR({$wpdb->posts}.post_date)=" . substr( $q['m'], 0, 4 );
2056 if ( strlen( $q['m'] ) > 5 ) {
2057 $where .= " AND MONTH({$wpdb->posts}.post_date)=" . substr( $q['m'], 4, 2 );
2058 }
2059 if ( strlen( $q['m'] ) > 7 ) {
2060 $where .= " AND DAYOFMONTH({$wpdb->posts}.post_date)=" . substr( $q['m'], 6, 2 );
2061 }
2062 if ( strlen( $q['m'] ) > 9 ) {
2063 $where .= " AND HOUR({$wpdb->posts}.post_date)=" . substr( $q['m'], 8, 2 );
2064 }
2065 if ( strlen( $q['m'] ) > 11 ) {
2066 $where .= " AND MINUTE({$wpdb->posts}.post_date)=" . substr( $q['m'], 10, 2 );
2067 }
2068 if ( strlen( $q['m'] ) > 13 ) {
2069 $where .= " AND SECOND({$wpdb->posts}.post_date)=" . substr( $q['m'], 12, 2 );
2070 }
2071 }
2072
2073 // Handle the other individual date parameters.
2074 $date_parameters = array();
2075
2076 if ( '' !== $q['hour'] ) {
2077 $date_parameters['hour'] = $q['hour'];
2078 }
2079
2080 if ( '' !== $q['minute'] ) {
2081 $date_parameters['minute'] = $q['minute'];
2082 }
2083
2084 if ( '' !== $q['second'] ) {
2085 $date_parameters['second'] = $q['second'];
2086 }
2087
2088 if ( $q['year'] ) {
2089 $date_parameters['year'] = $q['year'];
2090 }
2091
2092 if ( $q['monthnum'] ) {
2093 $date_parameters['monthnum'] = $q['monthnum'];
2094 }
2095
2096 if ( $q['w'] ) {
2097 $date_parameters['week'] = $q['w'];
2098 }
2099
2100 if ( $q['day'] ) {
2101 $date_parameters['day'] = $q['day'];
2102 }
2103
2104 if ( $date_parameters ) {
2105 $date_query = new WP_Date_Query( array( $date_parameters ) );
2106 $where .= $date_query->get_sql();
2107 }
2108 unset( $date_parameters, $date_query );
2109
2110 // Handle complex date queries.
2111 if ( ! empty( $q['date_query'] ) ) {
2112 $this->date_query = new WP_Date_Query( $q['date_query'] );
2113 $where .= $this->date_query->get_sql();
2114 }
2115
2116 // If we've got a post_type AND it's not "any" post_type.
2117 if ( ! empty( $q['post_type'] ) && 'any' !== $q['post_type'] ) {
2118 foreach ( (array) $q['post_type'] as $_post_type ) {
2119 $ptype_obj = get_post_type_object( $_post_type );
2120 if ( ! $ptype_obj || ! $ptype_obj->query_var || empty( $q[ $ptype_obj->query_var ] ) ) {
2121 continue;
2122 }
2123
2124 if ( ! $ptype_obj->hierarchical ) {
2125 // Non-hierarchical post types can directly use 'name'.
2126 $q['name'] = $q[ $ptype_obj->query_var ];
2127 } else {
2128 // Hierarchical post types will operate through 'pagename'.
2129 $q['pagename'] = $q[ $ptype_obj->query_var ];
2130 $q['name'] = '';
2131 }
2132
2133 // Only one request for a slug is possible, this is why name & pagename are overwritten above.
2134 break;
2135 } // End foreach.
2136 unset( $ptype_obj );
2137 }
2138
2139 if ( '' !== $q['title'] ) {
2140 $where .= $wpdb->prepare( " AND {$wpdb->posts}.post_title = %s", stripslashes( $q['title'] ) );
2141 }
2142
2143 // Parameters related to 'post_name'.
2144 if ( '' !== $q['name'] ) {
2145 $q['name'] = sanitize_title_for_query( $q['name'] );
2146 $where .= " AND {$wpdb->posts}.post_name = '" . $q['name'] . "'";
2147 } elseif ( '' !== $q['pagename'] ) {
2148 if ( isset( $this->queried_object_id ) ) {
2149 $reqpage = $this->queried_object_id;
2150 } else {
2151 if ( 'page' !== $q['post_type'] ) {
2152 foreach ( (array) $q['post_type'] as $_post_type ) {
2153 $ptype_obj = get_post_type_object( $_post_type );
2154 if ( ! $ptype_obj || ! $ptype_obj->hierarchical ) {
2155 continue;
2156 }
2157
2158 $reqpage = get_page_by_path( $q['pagename'], OBJECT, $_post_type );
2159 if ( $reqpage ) {
2160 break;
2161 }
2162 }
2163 unset( $ptype_obj );
2164 } else {
2165 $reqpage = get_page_by_path( $q['pagename'] );
2166 }
2167 if ( ! empty( $reqpage ) ) {
2168 $reqpage = $reqpage->ID;
2169 } else {
2170 $reqpage = 0;
2171 }
2172 }
2173
2174 $page_for_posts = get_option( 'page_for_posts' );
2175 if ( ( 'page' !== get_option( 'show_on_front' ) ) || empty( $page_for_posts ) || ( $reqpage != $page_for_posts ) ) {
2176 $q['pagename'] = sanitize_title_for_query( wp_basename( $q['pagename'] ) );
2177 $q['name'] = $q['pagename'];
2178 $where .= " AND ({$wpdb->posts}.ID = '$reqpage')";
2179 $reqpage_obj = get_post( $reqpage );
2180 if ( is_object( $reqpage_obj ) && 'attachment' === $reqpage_obj->post_type ) {
2181 $this->is_attachment = true;
2182 $post_type = 'attachment';
2183 $q['post_type'] = 'attachment';
2184 $this->is_page = true;
2185 $q['attachment_id'] = $reqpage;
2186 }
2187 }
2188 } elseif ( '' !== $q['attachment'] ) {
2189 $q['attachment'] = sanitize_title_for_query( wp_basename( $q['attachment'] ) );
2190 $q['name'] = $q['attachment'];
2191 $where .= " AND {$wpdb->posts}.post_name = '" . $q['attachment'] . "'";
2192 } elseif ( is_array( $q['post_name__in'] ) && ! empty( $q['post_name__in'] ) ) {
2193 $q['post_name__in'] = array_map( 'sanitize_title_for_query', $q['post_name__in'] );
2194 $post_name__in = "'" . implode( "','", $q['post_name__in'] ) . "'";
2195 $where .= " AND {$wpdb->posts}.post_name IN ($post_name__in)";
2196 }
2197
2198 // If an attachment is requested by number, let it supersede any post number.
2199 if ( $q['attachment_id'] ) {
2200 $q['p'] = absint( $q['attachment_id'] );
2201 }
2202
2203 // If a post number is specified, load that post.
2204 if ( $q['p'] ) {
2205 $where .= " AND {$wpdb->posts}.ID = " . $q['p'];
2206 } elseif ( $q['post__in'] ) {
2207 $post__in = implode( ',', array_map( 'absint', $q['post__in'] ) );
2208 $where .= " AND {$wpdb->posts}.ID IN ($post__in)";
2209 } elseif ( $q['post__not_in'] ) {
2210 $post__not_in = implode( ',', array_map( 'absint', $q['post__not_in'] ) );
2211 $where .= " AND {$wpdb->posts}.ID NOT IN ($post__not_in)";
2212 }
2213
2214 if ( is_numeric( $q['post_parent'] ) ) {
2215 $where .= $wpdb->prepare( " AND {$wpdb->posts}.post_parent = %d ", $q['post_parent'] );
2216 } elseif ( $q['post_parent__in'] ) {
2217 $post_parent__in = implode( ',', array_map( 'absint', $q['post_parent__in'] ) );
2218 $where .= " AND {$wpdb->posts}.post_parent IN ($post_parent__in)";
2219 } elseif ( $q['post_parent__not_in'] ) {
2220 $post_parent__not_in = implode( ',', array_map( 'absint', $q['post_parent__not_in'] ) );
2221 $where .= " AND {$wpdb->posts}.post_parent NOT IN ($post_parent__not_in)";
2222 }
2223
2224 if ( $q['page_id'] ) {
2225 if ( ( 'page' !== get_option( 'show_on_front' ) ) || ( get_option( 'page_for_posts' ) != $q['page_id'] ) ) {
2226 $q['p'] = $q['page_id'];
2227 $where = " AND {$wpdb->posts}.ID = " . $q['page_id'];
2228 }
2229 }
2230
2231 // If a search pattern is specified, load the posts that match.
2232 if ( strlen( $q['s'] ) ) {
2233 $search = $this->parse_search( $q );
2234 }
2235
2236 if ( ! $q['suppress_filters'] ) {
2237 /**
2238 * Filters the search SQL that is used in the WHERE clause of WP_Query.
2239 *
2240 * @since 3.0.0
2241 *
2242 * @param string $search Search SQL for WHERE clause.
2243 * @param WP_Query $query The current WP_Query object.
2244 */
2245 $search = apply_filters_ref_array( 'posts_search', array( $search, &$this ) );
2246 }
2247
2248 // Taxonomies.
2249 if ( ! $this->is_singular ) {
2250 $this->parse_tax_query( $q );
2251
2252 $clauses = $this->tax_query->get_sql( $wpdb->posts, 'ID' );
2253
2254 $join .= $clauses['join'];
2255 $where .= $clauses['where'];
2256 }
2257
2258 if ( $this->is_tax ) {
2259 if ( empty( $post_type ) ) {
2260 // Do a fully inclusive search for currently registered post types of queried taxonomies.
2261 $post_type = array();
2262 $taxonomies = array_keys( $this->tax_query->queried_terms );
2263 foreach ( get_post_types( array( 'exclude_from_search' => false ) ) as $pt ) {
2264 $object_taxonomies = 'attachment' === $pt ? get_taxonomies_for_attachments() : get_object_taxonomies( $pt );
2265 if ( array_intersect( $taxonomies, $object_taxonomies ) ) {
2266 $post_type[] = $pt;
2267 }
2268 }
2269 if ( ! $post_type ) {
2270 $post_type = 'any';
2271 } elseif ( count( $post_type ) === 1 ) {
2272 $post_type = $post_type[0];
2273 }
2274
2275 $post_status_join = true;
2276 } elseif ( in_array( 'attachment', (array) $post_type, true ) ) {
2277 $post_status_join = true;
2278 }
2279 }
2280
2281 /*
2282 * Ensure that 'taxonomy', 'term', 'term_id', 'cat', and
2283 * 'category_name' vars are set for backward compatibility.
2284 */
2285 if ( ! empty( $this->tax_query->queried_terms ) ) {
2286
2287 /*
2288 * Set 'taxonomy', 'term', and 'term_id' to the
2289 * first taxonomy other than 'post_tag' or 'category'.
2290 */
2291 if ( ! isset( $q['taxonomy'] ) ) {
2292 foreach ( $this->tax_query->queried_terms as $queried_taxonomy => $queried_items ) {
2293 if ( empty( $queried_items['terms'][0] ) ) {
2294 continue;
2295 }
2296
2297 if ( ! in_array( $queried_taxonomy, array( 'category', 'post_tag' ), true ) ) {
2298 $q['taxonomy'] = $queried_taxonomy;
2299
2300 if ( 'slug' === $queried_items['field'] ) {
2301 $q['term'] = $queried_items['terms'][0];
2302 } else {
2303 $q['term_id'] = $queried_items['terms'][0];
2304 }
2305
2306 // Take the first one we find.
2307 break;
2308 }
2309 }
2310 }
2311
2312 // 'cat', 'category_name', 'tag_id'.
2313 foreach ( $this->tax_query->queried_terms as $queried_taxonomy => $queried_items ) {
2314 if ( empty( $queried_items['terms'][0] ) ) {
2315 continue;
2316 }
2317
2318 if ( 'category' === $queried_taxonomy ) {
2319 $the_cat = get_term_by( $queried_items['field'], $queried_items['terms'][0], 'category' );
2320 if ( $the_cat ) {
2321 $this->set( 'cat', $the_cat->term_id );
2322 $this->set( 'category_name', $the_cat->slug );
2323 }
2324 unset( $the_cat );
2325 }
2326
2327 if ( 'post_tag' === $queried_taxonomy ) {
2328 $the_tag = get_term_by( $queried_items['field'], $queried_items['terms'][0], 'post_tag' );
2329 if ( $the_tag ) {
2330 $this->set( 'tag_id', $the_tag->term_id );
2331 }
2332 unset( $the_tag );
2333 }
2334 }
2335 }
2336
2337 if ( ! empty( $this->tax_query->queries ) || ! empty( $this->meta_query->queries ) || ! empty( $this->allow_query_attachment_by_filename ) ) {
2338 $groupby = "{$wpdb->posts}.ID";
2339 }
2340
2341 // Author/user stuff.
2342
2343 if ( ! empty( $q['author'] ) && '0' != $q['author'] ) {
2344 $q['author'] = addslashes_gpc( '' . urldecode( $q['author'] ) );
2345 $authors = array_unique( array_map( 'intval', preg_split( '/[,\s]+/', $q['author'] ) ) );
2346 foreach ( $authors as $author ) {
2347 $key = $author > 0 ? 'author__in' : 'author__not_in';
2348 $q[ $key ][] = abs( $author );
2349 }
2350 $q['author'] = implode( ',', $authors );
2351 }
2352
2353 if ( ! empty( $q['author__not_in'] ) ) {
2354 $author__not_in = implode( ',', array_map( 'absint', array_unique( (array) $q['author__not_in'] ) ) );
2355 $where .= " AND {$wpdb->posts}.post_author NOT IN ($author__not_in) ";
2356 } elseif ( ! empty( $q['author__in'] ) ) {
2357 $author__in = implode( ',', array_map( 'absint', array_unique( (array) $q['author__in'] ) ) );
2358 $where .= " AND {$wpdb->posts}.post_author IN ($author__in) ";
2359 }
2360
2361 // Author stuff for nice URLs.
2362
2363 if ( '' !== $q['author_name'] ) {
2364 if ( str_contains( $q['author_name'], '/' ) ) {
2365 $q['author_name'] = explode( '/', $q['author_name'] );
2366 if ( $q['author_name'][ count( $q['author_name'] ) - 1 ] ) {
2367 $q['author_name'] = $q['author_name'][ count( $q['author_name'] ) - 1 ]; // No trailing slash.
2368 } else {
2369 $q['author_name'] = $q['author_name'][ count( $q['author_name'] ) - 2 ]; // There was a trailing slash.
2370 }
2371 }
2372 $q['author_name'] = sanitize_title_for_query( $q['author_name'] );
2373 $q['author'] = get_user_by( 'slug', $q['author_name'] );
2374 if ( $q['author'] ) {
2375 $q['author'] = $q['author']->ID;
2376 }
2377 $whichauthor .= " AND ({$wpdb->posts}.post_author = " . absint( $q['author'] ) . ')';
2378 }
2379
2380 // Matching by comment count.
2381 if ( isset( $q['comment_count'] ) ) {
2382 // Numeric comment count is converted to array format.
2383 if ( is_numeric( $q['comment_count'] ) ) {
2384 $q['comment_count'] = array(
2385 'value' => (int) $q['comment_count'],
2386 );
2387 }
2388
2389 if ( isset( $q['comment_count']['value'] ) ) {
2390 $q['comment_count'] = array_merge(
2391 array(
2392 'compare' => '=',
2393 ),
2394 $q['comment_count']
2395 );
2396
2397 // Fallback for invalid compare operators is '='.
2398 $compare_operators = array( '=', '!=', '>', '>=', '<', '<=' );
2399 if ( ! in_array( $q['comment_count']['compare'], $compare_operators, true ) ) {
2400 $q['comment_count']['compare'] = '=';
2401 }
2402
2403 $where .= $wpdb->prepare( " AND {$wpdb->posts}.comment_count {$q['comment_count']['compare']} %d", $q['comment_count']['value'] );
2404 }
2405 }
2406
2407 // MIME-Type stuff for attachment browsing.
2408
2409 if ( isset( $q['post_mime_type'] ) && '' !== $q['post_mime_type'] ) {
2410 $whichmimetype = wp_post_mime_type_where( $q['post_mime_type'], $wpdb->posts );
2411 }
2412 $where .= $search . $whichauthor . $whichmimetype;
2413
2414 if ( ! empty( $this->allow_query_attachment_by_filename ) ) {
2415 $join .= " LEFT JOIN {$wpdb->postmeta} AS sq1 ON ( {$wpdb->posts}.ID = sq1.post_id AND sq1.meta_key = '_wp_attached_file' )";
2416 }
2417
2418 if ( ! empty( $this->meta_query->queries ) ) {
2419 $clauses = $this->meta_query->get_sql( 'post', $wpdb->posts, 'ID', $this );
2420 $join .= $clauses['join'];
2421 $where .= $clauses['where'];
2422 }
2423
2424 $rand = ( isset( $q['orderby'] ) && 'rand' === $q['orderby'] );
2425 if ( ! isset( $q['order'] ) ) {
2426 $q['order'] = $rand ? '' : 'DESC';
2427 } else {
2428 $q['order'] = $rand ? '' : $this->parse_order( $q['order'] );
2429 }
2430
2431 // These values of orderby should ignore the 'order' parameter.
2432 $force_asc = array( 'post__in', 'post_name__in', 'post_parent__in', 'author__in' );
2433 if ( isset( $q['orderby'] ) && in_array( $q['orderby'], $force_asc, true ) ) {
2434 $q['order'] = '';
2435 }
2436
2437 // Order by.
2438 if ( empty( $q['orderby'] ) ) {
2439 /*
2440 * Boolean false or empty array blanks out ORDER BY,
2441 * while leaving the value unset or otherwise empty sets the default.
2442 */
2443 if ( isset( $q['orderby'] ) && ( is_array( $q['orderby'] ) || false === $q['orderby'] ) ) {
2444 $orderby = '';
2445 } else {
2446 $orderby = "{$wpdb->posts}.post_date " . $q['order'];
2447 }
2448 } elseif ( 'none' === $q['orderby'] ) {
2449 $orderby = '';
2450 } else {
2451 $orderby_array = array();
2452 if ( is_array( $q['orderby'] ) ) {
2453 foreach ( $q['orderby'] as $_orderby => $order ) {
2454 $orderby = addslashes_gpc( urldecode( $_orderby ) );
2455 $parsed = $this->parse_orderby( $orderby );
2456
2457 if ( ! $parsed ) {
2458 continue;
2459 }
2460
2461 $orderby_array[] = $parsed . ' ' . $this->parse_order( $order );
2462 }
2463 $orderby = implode( ', ', $orderby_array );
2464
2465 } else {
2466 $q['orderby'] = urldecode( $q['orderby'] );
2467 $q['orderby'] = addslashes_gpc( $q['orderby'] );
2468
2469 foreach ( explode( ' ', $q['orderby'] ) as $i => $orderby ) {
2470 $parsed = $this->parse_orderby( $orderby );
2471 // Only allow certain values for safety.
2472 if ( ! $parsed ) {
2473 continue;
2474 }
2475
2476 $orderby_array[] = $parsed;
2477 }
2478 $orderby = implode( ' ' . $q['order'] . ', ', $orderby_array );
2479
2480 if ( empty( $orderby ) ) {
2481 $orderby = "{$wpdb->posts}.post_date " . $q['order'];
2482 } elseif ( ! empty( $q['order'] ) ) {
2483 $orderby .= " {$q['order']}";
2484 }
2485 }
2486 }
2487
2488 // Order search results by relevance only when another "orderby" is not specified in the query.
2489 if ( ! empty( $q['s'] ) ) {
2490 $search_orderby = '';
2491 if ( ! empty( $q['search_orderby_title'] ) && ( empty( $q['orderby'] ) && ! $this->is_feed ) || ( isset( $q['orderby'] ) && 'relevance' === $q['orderby'] ) ) {
2492 $search_orderby = $this->parse_search_order( $q );
2493 }
2494
2495 if ( ! $q['suppress_filters'] ) {
2496 /**
2497 * Filters the ORDER BY used when ordering search results.
2498 *
2499 * @since 3.7.0
2500 *
2501 * @param string $search_orderby The ORDER BY clause.
2502 * @param WP_Query $query The current WP_Query instance.
2503 */
2504 $search_orderby = apply_filters( 'posts_search_orderby', $search_orderby, $this );
2505 }
2506
2507 if ( $search_orderby ) {
2508 $orderby = $orderby ? $search_orderby . ', ' . $orderby : $search_orderby;
2509 }
2510 }
2511
2512 if ( is_array( $post_type ) && count( $post_type ) > 1 ) {
2513 $post_type_cap = 'multiple_post_type';
2514 } else {
2515 if ( is_array( $post_type ) ) {
2516 $post_type = reset( $post_type );
2517 }
2518 $post_type_object = get_post_type_object( $post_type );
2519 if ( empty( $post_type_object ) ) {
2520 $post_type_cap = $post_type;
2521 }
2522 }
2523
2524 if ( isset( $q['post_password'] ) ) {
2525 $where .= $wpdb->prepare( " AND {$wpdb->posts}.post_password = %s", $q['post_password'] );
2526 if ( empty( $q['perm'] ) ) {
2527 $q['perm'] = 'readable';
2528 }
2529 } elseif ( isset( $q['has_password'] ) ) {
2530 $where .= sprintf( " AND {$wpdb->posts}.post_password %s ''", $q['has_password'] ? '!=' : '=' );
2531 }
2532
2533 if ( ! empty( $q['comment_status'] ) ) {
2534 $where .= $wpdb->prepare( " AND {$wpdb->posts}.comment_status = %s ", $q['comment_status'] );
2535 }
2536
2537 if ( ! empty( $q['ping_status'] ) ) {
2538 $where .= $wpdb->prepare( " AND {$wpdb->posts}.ping_status = %s ", $q['ping_status'] );
2539 }
2540
2541 $skip_post_status = false;
2542 if ( 'any' === $post_type ) {
2543 $in_search_post_types = get_post_types( array( 'exclude_from_search' => false ) );
2544 if ( empty( $in_search_post_types ) ) {
2545 $post_type_where = ' AND 1=0 ';
2546 $skip_post_status = true;
2547 } else {
2548 $post_type_where = " AND {$wpdb->posts}.post_type IN ('" . implode( "', '", array_map( 'esc_sql', $in_search_post_types ) ) . "')";
2549 }
2550 } elseif ( ! empty( $post_type ) && is_array( $post_type ) ) {
2551 $post_type_where = " AND {$wpdb->posts}.post_type IN ('" . implode( "', '", esc_sql( $post_type ) ) . "')";
2552 } elseif ( ! empty( $post_type ) ) {
2553 $post_type_where = $wpdb->prepare( " AND {$wpdb->posts}.post_type = %s", $post_type );
2554 $post_type_object = get_post_type_object( $post_type );
2555 } elseif ( $this->is_attachment ) {
2556 $post_type_where = " AND {$wpdb->posts}.post_type = 'attachment'";
2557 $post_type_object = get_post_type_object( 'attachment' );
2558 } elseif ( $this->is_page ) {
2559 $post_type_where = " AND {$wpdb->posts}.post_type = 'page'";
2560 $post_type_object = get_post_type_object( 'page' );
2561 } else {
2562 $post_type_where = " AND {$wpdb->posts}.post_type = 'post'";
2563 $post_type_object = get_post_type_object( 'post' );
2564 }
2565
2566 $edit_cap = 'edit_post';
2567 $read_cap = 'read_post';
2568
2569 if ( ! empty( $post_type_object ) ) {
2570 $edit_others_cap = $post_type_object->cap->edit_others_posts;
2571 $read_private_cap = $post_type_object->cap->read_private_posts;
2572 } else {
2573 $edit_others_cap = 'edit_others_' . $post_type_cap . 's';
2574 $read_private_cap = 'read_private_' . $post_type_cap . 's';
2575 }
2576
2577 $user_id = get_current_user_id();
2578
2579 $q_status = array();
2580 if ( $skip_post_status ) {
2581 $where .= $post_type_where;
2582 } elseif ( ! empty( $q['post_status'] ) ) {
2583
2584 $where .= $post_type_where;
2585
2586 $statuswheres = array();
2587 $q_status = $q['post_status'];
2588 if ( ! is_array( $q_status ) ) {
2589 $q_status = explode( ',', $q_status );
2590 }
2591 $r_status = array();
2592 $p_status = array();
2593 $e_status = array();
2594 if ( in_array( 'any', $q_status, true ) ) {
2595 foreach ( get_post_stati( array( 'exclude_from_search' => true ) ) as $status ) {
2596 if ( ! in_array( $status, $q_status, true ) ) {
2597 $e_status[] = "{$wpdb->posts}.post_status <> '$status'";
2598 }
2599 }
2600 } else {
2601 foreach ( get_post_stati() as $status ) {
2602 if ( in_array( $status, $q_status, true ) ) {
2603 if ( 'private' === $status ) {
2604 $p_status[] = "{$wpdb->posts}.post_status = '$status'";
2605 } else {
2606 $r_status[] = "{$wpdb->posts}.post_status = '$status'";
2607 }
2608 }
2609 }
2610 }
2611
2612 if ( empty( $q['perm'] ) || 'readable' !== $q['perm'] ) {
2613 $r_status = array_merge( $r_status, $p_status );
2614 unset( $p_status );
2615 }
2616
2617 if ( ! empty( $e_status ) ) {
2618 $statuswheres[] = '(' . implode( ' AND ', $e_status ) . ')';
2619 }
2620 if ( ! empty( $r_status ) ) {
2621 if ( ! empty( $q['perm'] ) && 'editable' === $q['perm'] && ! current_user_can( $edit_others_cap ) ) {
2622 $statuswheres[] = "({$wpdb->posts}.post_author = $user_id " . 'AND (' . implode( ' OR ', $r_status ) . '))';
2623 } else {
2624 $statuswheres[] = '(' . implode( ' OR ', $r_status ) . ')';
2625 }
2626 }
2627 if ( ! empty( $p_status ) ) {
2628 if ( ! empty( $q['perm'] ) && 'readable' === $q['perm'] && ! current_user_can( $read_private_cap ) ) {
2629 $statuswheres[] = "({$wpdb->posts}.post_author = $user_id " . 'AND (' . implode( ' OR ', $p_status ) . '))';
2630 } else {
2631 $statuswheres[] = '(' . implode( ' OR ', $p_status ) . ')';
2632 }
2633 }
2634 if ( $post_status_join ) {
2635 $join .= " LEFT JOIN {$wpdb->posts} AS p2 ON ({$wpdb->posts}.post_parent = p2.ID) ";
2636 foreach ( $statuswheres as $index => $statuswhere ) {
2637 $statuswheres[ $index ] = "($statuswhere OR ({$wpdb->posts}.post_status = 'inherit' AND " . str_replace( $wpdb->posts, 'p2', $statuswhere ) . '))';
2638 }
2639 }
2640 $where_status = implode( ' OR ', $statuswheres );
2641 if ( ! empty( $where_status ) ) {
2642 $where .= " AND ($where_status)";
2643 }
2644 } elseif ( ! $this->is_singular ) {
2645 if ( 'any' === $post_type ) {
2646 $queried_post_types = get_post_types( array( 'exclude_from_search' => false ) );
2647 } elseif ( is_array( $post_type ) ) {
2648 $queried_post_types = $post_type;
2649 } elseif ( ! empty( $post_type ) ) {
2650 $queried_post_types = array( $post_type );
2651 } else {
2652 $queried_post_types = array( 'post' );
2653 }
2654
2655 if ( ! empty( $queried_post_types ) ) {
2656
2657 $status_type_clauses = array();
2658
2659 foreach ( $queried_post_types as $queried_post_type ) {
2660
2661 $queried_post_type_object = get_post_type_object( $queried_post_type );
2662
2663 $type_where = '(' . $wpdb->prepare( "{$wpdb->posts}.post_type = %s AND (", $queried_post_type );
2664
2665 // Public statuses.
2666 $public_statuses = get_post_stati( array( 'public' => true ) );
2667 $status_clauses = array();
2668 foreach ( $public_statuses as $public_status ) {
2669 $status_clauses[] = "{$wpdb->posts}.post_status = '$public_status'";
2670 }
2671 $type_where .= implode( ' OR ', $status_clauses );
2672
2673 // Add protected states that should show in the admin all list.
2674 if ( $this->is_admin ) {
2675 $admin_all_statuses = get_post_stati(
2676 array(
2677 'protected' => true,
2678 'show_in_admin_all_list' => true,
2679 )
2680 );
2681 foreach ( $admin_all_statuses as $admin_all_status ) {
2682 $type_where .= " OR {$wpdb->posts}.post_status = '$admin_all_status'";
2683 }
2684 }
2685
2686 // Add private states that are visible to current user.
2687 if ( is_user_logged_in() && $queried_post_type_object instanceof WP_Post_Type ) {
2688 $read_private_cap = $queried_post_type_object->cap->read_private_posts;
2689 $private_statuses = get_post_stati( array( 'private' => true ) );
2690 foreach ( $private_statuses as $private_status ) {
2691 $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')";
2692 }
2693 }
2694
2695 $type_where .= '))';
2696
2697 $status_type_clauses[] = $type_where;
2698 }
2699
2700 if ( ! empty( $status_type_clauses ) ) {
2701 $where .= ' AND (' . implode( ' OR ', $status_type_clauses ) . ')';
2702 }
2703 } else {
2704 $where .= ' AND 1=0 ';
2705 }
2706 } else {
2707 $where .= $post_type_where;
2708 }
2709
2710 /*
2711 * Apply filters on where and join prior to paging so that any
2712 * manipulations to them are reflected in the paging by day queries.
2713 */
2714 if ( ! $q['suppress_filters'] ) {
2715 /**
2716 * Filters the WHERE clause of the query.
2717 *
2718 * @since 1.5.0
2719 *
2720 * @param string $where The WHERE clause of the query.
2721 * @param WP_Query $query The WP_Query instance (passed by reference).
2722 */
2723 $where = apply_filters_ref_array( 'posts_where', array( $where, &$this ) );
2724
2725 /**
2726 * Filters the JOIN clause of the query.
2727 *
2728 * @since 1.5.0
2729 *
2730 * @param string $join The JOIN clause of the query.
2731 * @param WP_Query $query The WP_Query instance (passed by reference).
2732 */
2733 $join = apply_filters_ref_array( 'posts_join', array( $join, &$this ) );
2734 }
2735
2736 // Paging.
2737 if ( empty( $q['nopaging'] ) && ! $this->is_singular ) {
2738 $page = absint( $q['paged'] );
2739 if ( ! $page ) {
2740 $page = 1;
2741 }
2742
2743 // If 'offset' is provided, it takes precedence over 'paged'.
2744 if ( isset( $q['offset'] ) && is_numeric( $q['offset'] ) ) {
2745 $q['offset'] = absint( $q['offset'] );
2746 $pgstrt = $q['offset'] . ', ';
2747 } else {
2748 $pgstrt = absint( ( $page - 1 ) * $q['posts_per_page'] ) . ', ';
2749 }
2750 $limits = 'LIMIT ' . $pgstrt . $q['posts_per_page'];
2751 }
2752
2753 // Comments feeds.
2754 if ( $this->is_comment_feed && ! $this->is_singular ) {
2755 if ( $this->is_archive || $this->is_search ) {
2756 $cjoin = "JOIN {$wpdb->posts} ON ( {$wpdb->comments}.comment_post_ID = {$wpdb->posts}.ID ) $join ";
2757 $cwhere = "WHERE comment_approved = '1' $where";
2758 $cgroupby = "{$wpdb->comments}.comment_id";
2759 } else { // Other non-singular, e.g. front.
2760 $cjoin = "JOIN {$wpdb->posts} ON ( {$wpdb->comments}.comment_post_ID = {$wpdb->posts}.ID )";
2761 $cwhere = "WHERE ( post_status = 'publish' OR ( post_status = 'inherit' AND post_type = 'attachment' ) ) AND comment_approved = '1'";
2762 $cgroupby = '';
2763 }
2764
2765 if ( ! $q['suppress_filters'] ) {
2766 /**
2767 * Filters the JOIN clause of the comments feed query before sending.
2768 *
2769 * @since 2.2.0
2770 *
2771 * @param string $cjoin The JOIN clause of the query.
2772 * @param WP_Query $query The WP_Query instance (passed by reference).
2773 */
2774 $cjoin = apply_filters_ref_array( 'comment_feed_join', array( $cjoin, &$this ) );
2775
2776 /**
2777 * Filters the WHERE clause of the comments feed query before sending.
2778 *
2779 * @since 2.2.0
2780 *
2781 * @param string $cwhere The WHERE clause of the query.
2782 * @param WP_Query $query The WP_Query instance (passed by reference).
2783 */
2784 $cwhere = apply_filters_ref_array( 'comment_feed_where', array( $cwhere, &$this ) );
2785
2786 /**
2787 * Filters the GROUP BY clause of the comments feed query before sending.
2788 *
2789 * @since 2.2.0
2790 *
2791 * @param string $cgroupby The GROUP BY clause of the query.
2792 * @param WP_Query $query The WP_Query instance (passed by reference).
2793 */
2794 $cgroupby = apply_filters_ref_array( 'comment_feed_groupby', array( $cgroupby, &$this ) );
2795
2796 /**
2797 * Filters the ORDER BY clause of the comments feed query before sending.
2798 *
2799 * @since 2.8.0
2800 *
2801 * @param string $corderby The ORDER BY clause of the query.
2802 * @param WP_Query $query The WP_Query instance (passed by reference).
2803 */
2804 $corderby = apply_filters_ref_array( 'comment_feed_orderby', array( 'comment_date_gmt DESC', &$this ) );
2805
2806 /**
2807 * Filters the LIMIT clause of the comments feed query before sending.
2808 *
2809 * @since 2.8.0
2810 *
2811 * @param string $climits The JOIN clause of the query.
2812 * @param WP_Query $query The WP_Query instance (passed by reference).
2813 */
2814 $climits = apply_filters_ref_array( 'comment_feed_limits', array( 'LIMIT ' . get_option( 'posts_per_rss' ), &$this ) );
2815 }
2816
2817 $cgroupby = ( ! empty( $cgroupby ) ) ? 'GROUP BY ' . $cgroupby : '';
2818 $corderby = ( ! empty( $corderby ) ) ? 'ORDER BY ' . $corderby : '';
2819 $climits = ( ! empty( $climits ) ) ? $climits : '';
2820
2821 $comments_request = "SELECT $distinct {$wpdb->comments}.comment_ID FROM {$wpdb->comments} $cjoin $cwhere $cgroupby $corderby $climits";
2822
2823 $key = md5( $comments_request );
2824 $last_changed = wp_cache_get_last_changed( 'comment' ) . ':' . wp_cache_get_last_changed( 'posts' );
2825
2826 $cache_key = "comment_feed:$key:$last_changed";
2827 $comment_ids = wp_cache_get( $cache_key, 'comment-queries' );
2828 if ( false === $comment_ids ) {
2829 $comment_ids = $wpdb->get_col( $comments_request );
2830 wp_cache_add( $cache_key, $comment_ids, 'comment-queries' );
2831 }
2832 _prime_comment_caches( $comment_ids );
2833
2834 // Convert to WP_Comment.
2835 /** @var WP_Comment[] */
2836 $this->comments = array_map( 'get_comment', $comment_ids );
2837 $this->comment_count = count( $this->comments );
2838
2839 $post_ids = array();
2840
2841 foreach ( $this->comments as $comment ) {
2842 $post_ids[] = (int) $comment->comment_post_ID;
2843 }
2844
2845 $post_ids = implode( ',', $post_ids );
2846 $join = '';
2847 if ( $post_ids ) {
2848 $where = "AND {$wpdb->posts}.ID IN ($post_ids) ";
2849 } else {
2850 $where = 'AND 0';
2851 }
2852 }
2853
2854 $pieces = array( 'where', 'groupby', 'join', 'orderby', 'distinct', 'fields', 'limits' );
2855
2856 /*
2857 * Apply post-paging filters on where and join. Only plugins that
2858 * manipulate paging queries should use these hooks.
2859 */
2860 if ( ! $q['suppress_filters'] ) {
2861 /**
2862 * Filters the WHERE clause of the query.
2863 *
2864 * Specifically for manipulating paging queries.
2865 *
2866 * @since 1.5.0
2867 *
2868 * @param string $where The WHERE clause of the query.
2869 * @param WP_Query $query The WP_Query instance (passed by reference).
2870 */
2871 $where = apply_filters_ref_array( 'posts_where_paged', array( $where, &$this ) );
2872
2873 /**
2874 * Filters the GROUP BY clause of the query.
2875 *
2876 * @since 2.0.0
2877 *
2878 * @param string $groupby The GROUP BY clause of the query.
2879 * @param WP_Query $query The WP_Query instance (passed by reference).
2880 */
2881 $groupby = apply_filters_ref_array( 'posts_groupby', array( $groupby, &$this ) );
2882
2883 /**
2884 * Filters the JOIN clause of the query.
2885 *
2886 * Specifically for manipulating paging queries.
2887 *
2888 * @since 1.5.0
2889 *
2890 * @param string $join The JOIN clause of the query.
2891 * @param WP_Query $query The WP_Query instance (passed by reference).
2892 */
2893 $join = apply_filters_ref_array( 'posts_join_paged', array( $join, &$this ) );
2894
2895 /**
2896 * Filters the ORDER BY clause of the query.
2897 *
2898 * @since 1.5.1
2899 *
2900 * @param string $orderby The ORDER BY clause of the query.
2901 * @param WP_Query $query The WP_Query instance (passed by reference).
2902 */
2903 $orderby = apply_filters_ref_array( 'posts_orderby', array( $orderby, &$this ) );
2904
2905 /**
2906 * Filters the DISTINCT clause of the query.
2907 *
2908 * @since 2.1.0
2909 *
2910 * @param string $distinct The DISTINCT clause of the query.
2911 * @param WP_Query $query The WP_Query instance (passed by reference).
2912 */
2913 $distinct = apply_filters_ref_array( 'posts_distinct', array( $distinct, &$this ) );
2914
2915 /**
2916 * Filters the LIMIT clause of the query.
2917 *
2918 * @since 2.1.0
2919 *
2920 * @param string $limits The LIMIT clause of the query.
2921 * @param WP_Query $query The WP_Query instance (passed by reference).
2922 */
2923 $limits = apply_filters_ref_array( 'post_limits', array( $limits, &$this ) );
2924
2925 /**
2926 * Filters the SELECT clause of the query.
2927 *
2928 * @since 2.1.0
2929 *
2930 * @param string $fields The SELECT clause of the query.
2931 * @param WP_Query $query The WP_Query instance (passed by reference).
2932 */
2933 $fields = apply_filters_ref_array( 'posts_fields', array( $fields, &$this ) );
2934
2935 /**
2936 * Filters all query clauses at once, for convenience.
2937 *
2938 * Covers the WHERE, GROUP BY, JOIN, ORDER BY, DISTINCT,
2939 * fields (SELECT), and LIMIT clauses.
2940 *
2941 * @since 3.1.0
2942 *
2943 * @param string[] $clauses {
2944 * Associative array of the clauses for the query.
2945 *
2946 * @type string $where The WHERE clause of the query.
2947 * @type string $groupby The GROUP BY clause of the query.
2948 * @type string $join The JOIN clause of the query.
2949 * @type string $orderby The ORDER BY clause of the query.
2950 * @type string $distinct The DISTINCT clause of the query.
2951 * @type string $fields The SELECT clause of the query.
2952 * @type string $limits The LIMIT clause of the query.
2953 * }
2954 * @param WP_Query $query The WP_Query instance (passed by reference).
2955 */
2956 $clauses = (array) apply_filters_ref_array( 'posts_clauses', array( compact( $pieces ), &$this ) );
2957
2958 $where = isset( $clauses['where'] ) ? $clauses['where'] : '';
2959 $groupby = isset( $clauses['groupby'] ) ? $clauses['groupby'] : '';
2960 $join = isset( $clauses['join'] ) ? $clauses['join'] : '';
2961 $orderby = isset( $clauses['orderby'] ) ? $clauses['orderby'] : '';
2962 $distinct = isset( $clauses['distinct'] ) ? $clauses['distinct'] : '';
2963 $fields = isset( $clauses['fields'] ) ? $clauses['fields'] : '';
2964 $limits = isset( $clauses['limits'] ) ? $clauses['limits'] : '';
2965 }
2966
2967 /**
2968 * Fires to announce the query's current selection parameters.
2969 *
2970 * For use by caching plugins.
2971 *
2972 * @since 2.3.0
2973 *
2974 * @param string $selection The assembled selection query.
2975 */
2976 do_action( 'posts_selection', $where . $groupby . $orderby . $limits . $join );
2977
2978 /*
2979 * Filters again for the benefit of caching plugins.
2980 * Regular plugins should use the hooks above.
2981 */
2982 if ( ! $q['suppress_filters'] ) {
2983 /**
2984 * Filters the WHERE clause of the query.
2985 *
2986 * For use by caching plugins.
2987 *
2988 * @since 2.5.0
2989 *
2990 * @param string $where The WHERE clause of the query.
2991 * @param WP_Query $query The WP_Query instance (passed by reference).
2992 */
2993 $where = apply_filters_ref_array( 'posts_where_request', array( $where, &$this ) );
2994
2995 /**
2996 * Filters the GROUP BY clause of the query.
2997 *
2998 * For use by caching plugins.
2999 *
3000 * @since 2.5.0
3001 *
3002 * @param string $groupby The GROUP BY clause of the query.
3003 * @param WP_Query $query The WP_Query instance (passed by reference).
3004 */
3005 $groupby = apply_filters_ref_array( 'posts_groupby_request', array( $groupby, &$this ) );
3006
3007 /**
3008 * Filters the JOIN clause of the query.
3009 *
3010 * For use by caching plugins.
3011 *
3012 * @since 2.5.0
3013 *
3014 * @param string $join The JOIN clause of the query.
3015 * @param WP_Query $query The WP_Query instance (passed by reference).
3016 */
3017 $join = apply_filters_ref_array( 'posts_join_request', array( $join, &$this ) );
3018
3019 /**
3020 * Filters the ORDER BY clause of the query.
3021 *
3022 * For use by caching plugins.
3023 *
3024 * @since 2.5.0
3025 *
3026 * @param string $orderby The ORDER BY clause of the query.
3027 * @param WP_Query $query The WP_Query instance (passed by reference).
3028 */
3029 $orderby = apply_filters_ref_array( 'posts_orderby_request', array( $orderby, &$this ) );
3030
3031 /**
3032 * Filters the DISTINCT clause of the query.
3033 *
3034 * For use by caching plugins.
3035 *
3036 * @since 2.5.0
3037 *
3038 * @param string $distinct The DISTINCT clause of the query.
3039 * @param WP_Query $query The WP_Query instance (passed by reference).
3040 */
3041 $distinct = apply_filters_ref_array( 'posts_distinct_request', array( $distinct, &$this ) );
3042
3043 /**
3044 * Filters the SELECT clause of the query.
3045 *
3046 * For use by caching plugins.
3047 *
3048 * @since 2.5.0
3049 *
3050 * @param string $fields The SELECT clause of the query.
3051 * @param WP_Query $query The WP_Query instance (passed by reference).
3052 */
3053 $fields = apply_filters_ref_array( 'posts_fields_request', array( $fields, &$this ) );
3054
3055 /**
3056 * Filters the LIMIT clause of the query.
3057 *
3058 * For use by caching plugins.
3059 *
3060 * @since 2.5.0
3061 *
3062 * @param string $limits The LIMIT clause of the query.
3063 * @param WP_Query $query The WP_Query instance (passed by reference).
3064 */
3065 $limits = apply_filters_ref_array( 'post_limits_request', array( $limits, &$this ) );
3066
3067 /**
3068 * Filters all query clauses at once, for convenience.
3069 *
3070 * For use by caching plugins.
3071 *
3072 * Covers the WHERE, GROUP BY, JOIN, ORDER BY, DISTINCT,
3073 * fields (SELECT), and LIMIT clauses.
3074 *
3075 * @since 3.1.0
3076 *
3077 * @param string[] $clauses {
3078 * Associative array of the clauses for the query.
3079 *
3080 * @type string $where The WHERE clause of the query.
3081 * @type string $groupby The GROUP BY clause of the query.
3082 * @type string $join The JOIN clause of the query.
3083 * @type string $orderby The ORDER BY clause of the query.
3084 * @type string $distinct The DISTINCT clause of the query.
3085 * @type string $fields The SELECT clause of the query.
3086 * @type string $limits The LIMIT clause of the query.
3087 * }
3088 * @param WP_Query $query The WP_Query instance (passed by reference).
3089 */
3090 $clauses = (array) apply_filters_ref_array( 'posts_clauses_request', array( compact( $pieces ), &$this ) );
3091
3092 $where = isset( $clauses['where'] ) ? $clauses['where'] : '';
3093 $groupby = isset( $clauses['groupby'] ) ? $clauses['groupby'] : '';
3094 $join = isset( $clauses['join'] ) ? $clauses['join'] : '';
3095 $orderby = isset( $clauses['orderby'] ) ? $clauses['orderby'] : '';
3096 $distinct = isset( $clauses['distinct'] ) ? $clauses['distinct'] : '';
3097 $fields = isset( $clauses['fields'] ) ? $clauses['fields'] : '';
3098 $limits = isset( $clauses['limits'] ) ? $clauses['limits'] : '';
3099 }
3100
3101 if ( ! empty( $groupby ) ) {
3102 $groupby = 'GROUP BY ' . $groupby;
3103 }
3104 if ( ! empty( $orderby ) ) {
3105 $orderby = 'ORDER BY ' . $orderby;
3106 }
3107
3108 $found_rows = '';
3109 if ( ! $q['no_found_rows'] && ! empty( $limits ) ) {
3110 $found_rows = 'SQL_CALC_FOUND_ROWS';
3111 }
3112
3113 $old_request = "
3114 SELECT $found_rows $distinct $fields
3115 FROM {$wpdb->posts} $join
3116 WHERE 1=1 $where
3117 $groupby
3118 $orderby
3119 $limits
3120 ";
3121
3122 $this->request = $old_request;
3123
3124 if ( ! $q['suppress_filters'] ) {
3125 /**
3126 * Filters the completed SQL query before sending.
3127 *
3128 * @since 2.0.0
3129 *
3130 * @param string $request The complete SQL query.
3131 * @param WP_Query $query The WP_Query instance (passed by reference).
3132 */
3133 $this->request = apply_filters_ref_array( 'posts_request', array( $this->request, &$this ) );
3134 }
3135
3136 /**
3137 * Filters the posts array before the query takes place.
3138 *
3139 * Return a non-null value to bypass WordPress' default post queries.
3140 *
3141 * Filtering functions that require pagination information are encouraged to set
3142 * the `found_posts` and `max_num_pages` properties of the WP_Query object,
3143 * passed to the filter by reference. If WP_Query does not perform a database
3144 * query, it will not have enough information to generate these values itself.
3145 *
3146 * @since 4.6.0
3147 *
3148 * @param WP_Post[]|int[]|null $posts Return an array of post data to short-circuit WP's query,
3149 * or null to allow WP to run its normal queries.
3150 * @param WP_Query $query The WP_Query instance (passed by reference).
3151 */
3152 $this->posts = apply_filters_ref_array( 'posts_pre_query', array( null, &$this ) );
3153
3154 /*
3155 * Ensure the ID database query is able to be cached.
3156 *
3157 * Random queries are expected to have unpredictable results and
3158 * cannot be cached. Note the space before `RAND` in the string
3159 * search, that to ensure against a collision with another
3160 * function.
3161 *
3162 * If `$fields` has been modified by the `posts_fields`,
3163 * `posts_fields_request`, `post_clauses` or `posts_clauses_request`
3164 * filters, then caching is disabled to prevent caching collisions.
3165 */
3166 $id_query_is_cacheable = ! str_contains( strtoupper( $orderby ), ' RAND(' );
3167
3168 $cacheable_field_values = array(
3169 "{$wpdb->posts}.*",
3170 "{$wpdb->posts}.ID, {$wpdb->posts}.post_parent",
3171 "{$wpdb->posts}.ID",
3172 );
3173
3174 if ( ! in_array( $fields, $cacheable_field_values, true ) ) {
3175 $id_query_is_cacheable = false;
3176 }
3177
3178 if ( $q['cache_results'] && $id_query_is_cacheable ) {
3179 $new_request = str_replace( $fields, "{$wpdb->posts}.*", $this->request );
3180 $cache_key = $this->generate_cache_key( $q, $new_request );
3181
3182 $cache_found = false;
3183 if ( null === $this->posts ) {
3184 $cached_results = wp_cache_get( $cache_key, 'post-queries', false, $cache_found );
3185
3186 if ( $cached_results ) {
3187 /** @var int[] */
3188 $post_ids = array_map( 'intval', $cached_results['posts'] );
3189
3190 $this->post_count = count( $post_ids );
3191 $this->found_posts = $cached_results['found_posts'];
3192 $this->max_num_pages = $cached_results['max_num_pages'];
3193
3194 if ( 'ids' === $q['fields'] ) {
3195 $this->posts = $post_ids;
3196
3197 return $this->posts;
3198 } elseif ( 'id=>parent' === $q['fields'] ) {
3199 _prime_post_parent_id_caches( $post_ids );
3200
3201 $post_parent_cache_keys = array();
3202 foreach ( $post_ids as $post_id ) {
3203 $post_parent_cache_keys[] = 'post_parent:' . (string) $post_id;
3204 }
3205
3206 /** @var int[] */
3207 $post_parents = wp_cache_get_multiple( $post_parent_cache_keys, 'posts' );
3208
3209 foreach ( $post_parents as $cache_key => $post_parent ) {
3210 $obj = new stdClass();
3211 $obj->ID = (int) str_replace( 'post_parent:', '', $cache_key );
3212 $obj->post_parent = (int) $post_parent;
3213
3214 $this->posts[] = $obj;
3215 }
3216
3217 return $post_parents;
3218 } else {
3219 _prime_post_caches( $post_ids, $q['update_post_term_cache'], $q['update_post_meta_cache'] );
3220 /** @var WP_Post[] */
3221 $this->posts = array_map( 'get_post', $post_ids );
3222 }
3223 }
3224 }
3225 }
3226
3227 if ( 'ids' === $q['fields'] ) {
3228 if ( null === $this->posts ) {
3229 $this->posts = $wpdb->get_col( $this->request );
3230 }
3231
3232 /** @var int[] */
3233 $this->posts = array_map( 'intval', $this->posts );
3234 $this->post_count = count( $this->posts );
3235 $this->set_found_posts( $q, $limits );
3236
3237 if ( $q['cache_results'] && $id_query_is_cacheable ) {
3238 $cache_value = array(
3239 'posts' => $this->posts,
3240 'found_posts' => $this->found_posts,
3241 'max_num_pages' => $this->max_num_pages,
3242 );
3243
3244 wp_cache_set( $cache_key, $cache_value, 'post-queries' );
3245 }
3246
3247 return $this->posts;
3248 }
3249
3250 if ( 'id=>parent' === $q['fields'] ) {
3251 if ( null === $this->posts ) {
3252 $this->posts = $wpdb->get_results( $this->request );
3253 }
3254
3255 $this->post_count = count( $this->posts );
3256 $this->set_found_posts( $q, $limits );
3257
3258 /** @var int[] */
3259 $post_parents = array();
3260 $post_ids = array();
3261 $post_parents_cache = array();
3262
3263 foreach ( $this->posts as $key => $post ) {
3264 $this->posts[ $key ]->ID = (int) $post->ID;
3265 $this->posts[ $key ]->post_parent = (int) $post->post_parent;
3266
3267 $post_parents[ (int) $post->ID ] = (int) $post->post_parent;
3268 $post_ids[] = (int) $post->ID;
3269
3270 $post_parents_cache[ 'post_parent:' . (string) $post->ID ] = (int) $post->post_parent;
3271 }
3272 // Prime post parent caches, so that on second run, there is not another database query.
3273 wp_cache_add_multiple( $post_parents_cache, 'posts' );
3274
3275 if ( $q['cache_results'] && $id_query_is_cacheable ) {
3276 $cache_value = array(
3277 'posts' => $post_ids,
3278 'found_posts' => $this->found_posts,
3279 'max_num_pages' => $this->max_num_pages,
3280 );
3281
3282 wp_cache_set( $cache_key, $cache_value, 'post-queries' );
3283 }
3284
3285 return $post_parents;
3286 }
3287
3288 $is_unfiltered_query = $old_request == $this->request && "{$wpdb->posts}.*" === $fields;
3289
3290 if ( null === $this->posts ) {
3291 $split_the_query = (
3292 $is_unfiltered_query
3293 && (
3294 wp_using_ext_object_cache()
3295 || ( ! empty( $limits ) && $q['posts_per_page'] < 500 )
3296 )
3297 );
3298
3299 /**
3300 * Filters whether to split the query.
3301 *
3302 * Splitting the query will cause it to fetch just the IDs of the found posts
3303 * (and then individually fetch each post by ID), rather than fetching every
3304 * complete row at once. One massive result vs. many small results.
3305 *
3306 * @since 3.4.0
3307 *
3308 * @param bool $split_the_query Whether or not to split the query.
3309 * @param WP_Query $query The WP_Query instance.
3310 */
3311 $split_the_query = apply_filters( 'split_the_query', $split_the_query, $this );
3312
3313 if ( $split_the_query ) {
3314 // First get the IDs and then fill in the objects.
3315
3316 $this->request = "
3317 SELECT $found_rows $distinct {$wpdb->posts}.ID
3318 FROM {$wpdb->posts} $join
3319 WHERE 1=1 $where
3320 $groupby
3321 $orderby
3322 $limits
3323 ";
3324
3325 /**
3326 * Filters the Post IDs SQL request before sending.
3327 *
3328 * @since 3.4.0
3329 *
3330 * @param string $request The post ID request.
3331 * @param WP_Query $query The WP_Query instance.
3332 */
3333 $this->request = apply_filters( 'posts_request_ids', $this->request, $this );
3334
3335 $post_ids = $wpdb->get_col( $this->request );
3336
3337 if ( $post_ids ) {
3338 $this->posts = $post_ids;
3339 $this->set_found_posts( $q, $limits );
3340 _prime_post_caches( $post_ids, $q['update_post_term_cache'], $q['update_post_meta_cache'] );
3341 } else {
3342 $this->posts = array();
3343 }
3344 } else {
3345 $this->posts = $wpdb->get_results( $this->request );
3346 $this->set_found_posts( $q, $limits );
3347 }
3348 }
3349
3350 // Convert to WP_Post objects.
3351 if ( $this->posts ) {
3352 /** @var WP_Post[] */
3353 $this->posts = array_map( 'get_post', $this->posts );
3354 }
3355
3356 $unfiltered_posts = $this->posts;
3357
3358 if ( $q['cache_results'] && $id_query_is_cacheable && ! $cache_found ) {
3359 $post_ids = wp_list_pluck( $this->posts, 'ID' );
3360
3361 $cache_value = array(
3362 'posts' => $post_ids,
3363 'found_posts' => $this->found_posts,
3364 'max_num_pages' => $this->max_num_pages,
3365 );
3366
3367 wp_cache_set( $cache_key, $cache_value, 'post-queries' );
3368 }
3369
3370 if ( ! $q['suppress_filters'] ) {
3371 /**
3372 * Filters the raw post results array, prior to status checks.
3373 *
3374 * @since 2.3.0
3375 *
3376 * @param WP_Post[] $posts Array of post objects.
3377 * @param WP_Query $query The WP_Query instance (passed by reference).
3378 */
3379 $this->posts = apply_filters_ref_array( 'posts_results', array( $this->posts, &$this ) );
3380 }
3381
3382 if ( ! empty( $this->posts ) && $this->is_comment_feed && $this->is_singular ) {
3383 /** This filter is documented in wp-includes/query.php */
3384 $cjoin = apply_filters_ref_array( 'comment_feed_join', array( '', &$this ) );
3385
3386 /** This filter is documented in wp-includes/query.php */
3387 $cwhere = apply_filters_ref_array( 'comment_feed_where', array( "WHERE comment_post_ID = '{$this->posts[0]->ID}' AND comment_approved = '1'", &$this ) );
3388
3389 /** This filter is documented in wp-includes/query.php */
3390 $cgroupby = apply_filters_ref_array( 'comment_feed_groupby', array( '', &$this ) );
3391 $cgroupby = ( ! empty( $cgroupby ) ) ? 'GROUP BY ' . $cgroupby : '';
3392
3393 /** This filter is documented in wp-includes/query.php */
3394 $corderby = apply_filters_ref_array( 'comment_feed_orderby', array( 'comment_date_gmt DESC', &$this ) );
3395 $corderby = ( ! empty( $corderby ) ) ? 'ORDER BY ' . $corderby : '';
3396
3397 /** This filter is documented in wp-includes/query.php */
3398 $climits = apply_filters_ref_array( 'comment_feed_limits', array( 'LIMIT ' . get_option( 'posts_per_rss' ), &$this ) );
3399
3400 $comments_request = "SELECT {$wpdb->comments}.comment_ID FROM {$wpdb->comments} $cjoin $cwhere $cgroupby $corderby $climits";
3401
3402 $comment_key = md5( $comments_request );
3403 $comment_last_changed = wp_cache_get_last_changed( 'comment' );
3404
3405 $comment_cache_key = "comment_feed:$comment_key:$comment_last_changed";
3406 $comment_ids = wp_cache_get( $comment_cache_key, 'comment-queries' );
3407 if ( false === $comment_ids ) {
3408 $comment_ids = $wpdb->get_col( $comments_request );
3409 wp_cache_add( $comment_cache_key, $comment_ids, 'comment-queries' );
3410 }
3411 _prime_comment_caches( $comment_ids );
3412
3413 // Convert to WP_Comment.
3414 /** @var WP_Comment[] */
3415 $this->comments = array_map( 'get_comment', $comment_ids );
3416 $this->comment_count = count( $this->comments );
3417 }
3418
3419 // Check post status to determine if post should be displayed.
3420 if ( ! empty( $this->posts ) && ( $this->is_single || $this->is_page ) ) {
3421 $status = get_post_status( $this->posts[0] );
3422
3423 if ( 'attachment' === $this->posts[0]->post_type && 0 === (int) $this->posts[0]->post_parent ) {
3424 $this->is_page = false;
3425 $this->is_single = true;
3426 $this->is_attachment = true;
3427 }
3428
3429 // If the post_status was specifically requested, let it pass through.
3430 if ( ! in_array( $status, $q_status, true ) ) {
3431 $post_status_obj = get_post_status_object( $status );
3432
3433 if ( $post_status_obj && ! $post_status_obj->public ) {
3434 if ( ! is_user_logged_in() ) {
3435 // User must be logged in to view unpublished posts.
3436 $this->posts = array();
3437 } else {
3438 if ( $post_status_obj->protected ) {
3439 // User must have edit permissions on the draft to preview.
3440 if ( ! current_user_can( $edit_cap, $this->posts[0]->ID ) ) {
3441 $this->posts = array();
3442 } else {
3443 $this->is_preview = true;
3444 if ( 'future' !== $status ) {
3445 $this->posts[0]->post_date = current_time( 'mysql' );
3446 }
3447 }
3448 } elseif ( $post_status_obj->private ) {
3449 if ( ! current_user_can( $read_cap, $this->posts[0]->ID ) ) {
3450 $this->posts = array();
3451 }
3452 } else {
3453 $this->posts = array();
3454 }
3455 }
3456 } elseif ( ! $post_status_obj ) {
3457 // Post status is not registered, assume it's not public.
3458 if ( ! current_user_can( $edit_cap, $this->posts[0]->ID ) ) {
3459 $this->posts = array();
3460 }
3461 }
3462 }
3463
3464 if ( $this->is_preview && $this->posts && current_user_can( $edit_cap, $this->posts[0]->ID ) ) {
3465 /**
3466 * Filters the single post for preview mode.
3467 *
3468 * @since 2.7.0
3469 *
3470 * @param WP_Post $post_preview The Post object.
3471 * @param WP_Query $query The WP_Query instance (passed by reference).
3472 */
3473 $this->posts[0] = get_post( apply_filters_ref_array( 'the_preview', array( $this->posts[0], &$this ) ) );
3474 }
3475 }
3476
3477 // Put sticky posts at the top of the posts array.
3478 $sticky_posts = get_option( 'sticky_posts' );
3479 if ( $this->is_home && $page <= 1 && is_array( $sticky_posts ) && ! empty( $sticky_posts ) && ! $q['ignore_sticky_posts'] ) {
3480 $num_posts = count( $this->posts );
3481 $sticky_offset = 0;
3482 // Loop over posts and relocate stickies to the front.
3483 for ( $i = 0; $i < $num_posts; $i++ ) {
3484 if ( in_array( $this->posts[ $i ]->ID, $sticky_posts, true ) ) {
3485 $sticky_post = $this->posts[ $i ];
3486 // Remove sticky from current position.
3487 array_splice( $this->posts, $i, 1 );
3488 // Move to front, after other stickies.
3489 array_splice( $this->posts, $sticky_offset, 0, array( $sticky_post ) );
3490 // Increment the sticky offset. The next sticky will be placed at this offset.
3491 ++$sticky_offset;
3492 // Remove post from sticky posts array.
3493 $offset = array_search( $sticky_post->ID, $sticky_posts, true );
3494 unset( $sticky_posts[ $offset ] );
3495 }
3496 }
3497
3498 // If any posts have been excluded specifically, Ignore those that are sticky.
3499 if ( ! empty( $sticky_posts ) && ! empty( $q['post__not_in'] ) ) {
3500 $sticky_posts = array_diff( $sticky_posts, $q['post__not_in'] );
3501 }
3502
3503 // Fetch sticky posts that weren't in the query results.
3504 if ( ! empty( $sticky_posts ) ) {
3505 $stickies = get_posts(
3506 array(
3507 'post__in' => $sticky_posts,
3508 'post_type' => $post_type,
3509 'post_status' => 'publish',
3510 'posts_per_page' => count( $sticky_posts ),
3511 'suppress_filters' => $q['suppress_filters'],
3512 'cache_results' => $q['cache_results'],
3513 'update_post_meta_cache' => $q['update_post_meta_cache'],
3514 'update_post_term_cache' => $q['update_post_term_cache'],
3515 'lazy_load_term_meta' => $q['lazy_load_term_meta'],
3516 )
3517 );
3518
3519 foreach ( $stickies as $sticky_post ) {
3520 array_splice( $this->posts, $sticky_offset, 0, array( $sticky_post ) );
3521 ++$sticky_offset;
3522 }
3523 }
3524 }
3525
3526 if ( ! $q['suppress_filters'] ) {
3527 /**
3528 * Filters the array of retrieved posts after they've been fetched and
3529 * internally processed.
3530 *
3531 * @since 1.5.0
3532 *
3533 * @param WP_Post[] $posts Array of post objects.
3534 * @param WP_Query $query The WP_Query instance (passed by reference).
3535 */
3536 $this->posts = apply_filters_ref_array( 'the_posts', array( $this->posts, &$this ) );
3537 }
3538
3539 /*
3540 * Ensure that any posts added/modified via one of the filters above are
3541 * of the type WP_Post and are filtered.
3542 */
3543 if ( $this->posts ) {
3544 $this->post_count = count( $this->posts );
3545
3546 /** @var WP_Post[] */
3547 $this->posts = array_map( 'get_post', $this->posts );
3548
3549 if ( $q['cache_results'] ) {
3550 if ( $is_unfiltered_query && $unfiltered_posts === $this->posts ) {
3551 update_post_caches( $this->posts, $post_type, $q['update_post_term_cache'], $q['update_post_meta_cache'] );
3552 } else {
3553 $post_ids = wp_list_pluck( $this->posts, 'ID' );
3554 _prime_post_caches( $post_ids, $q['update_post_term_cache'], $q['update_post_meta_cache'] );
3555 }
3556 }
3557
3558 /** @var WP_Post */
3559 $this->post = reset( $this->posts );
3560 } else {
3561 $this->post_count = 0;
3562 $this->posts = array();
3563 }
3564
3565 if ( ! empty( $this->posts ) && $q['update_menu_item_cache'] ) {
3566 update_menu_item_cache( $this->posts );
3567 }
3568
3569 if ( $q['lazy_load_term_meta'] ) {
3570 wp_queue_posts_for_term_meta_lazyload( $this->posts );
3571 }
3572
3573 return $this->posts;
3574 }
3575
3576 /**
3577 * Sets up the amount of found posts and the number of pages (if limit clause was used)
3578 * for the current query.
3579 *
3580 * @since 3.5.0
3581 *
3582 * @global wpdb $wpdb WordPress database abstraction object.
3583 *
3584 * @param array $q Query variables.
3585 * @param string $limits LIMIT clauses of the query.
3586 */
3587 private function set_found_posts( $q, $limits ) {
3588 global $wpdb;
3589
3590 /*
3591 * Bail if posts is an empty array. Continue if posts is an empty string,
3592 * null, or false to accommodate caching plugins that fill posts later.
3593 */
3594 if ( $q['no_found_rows'] || ( is_array( $this->posts ) && ! $this->posts ) ) {
3595 return;
3596 }
3597
3598 if ( ! empty( $limits ) ) {
3599 /**
3600 * Filters the query to run for retrieving the found posts.
3601 *
3602 * @since 2.1.0
3603 *
3604 * @param string $found_posts_query The query to run to find the found posts.
3605 * @param WP_Query $query The WP_Query instance (passed by reference).
3606 */
3607 $found_posts_query = apply_filters_ref_array( 'found_posts_query', array( 'SELECT FOUND_ROWS()', &$this ) );
3608
3609 $this->found_posts = (int) $wpdb->get_var( $found_posts_query );
3610 } else {
3611 if ( is_array( $this->posts ) ) {
3612 $this->found_posts = count( $this->posts );
3613 } else {
3614 if ( null === $this->posts ) {
3615 $this->found_posts = 0;
3616 } else {
3617 $this->found_posts = 1;
3618 }
3619 }
3620 }
3621
3622 /**
3623 * Filters the number of found posts for the query.
3624 *
3625 * @since 2.1.0
3626 *
3627 * @param int $found_posts The number of posts found.
3628 * @param WP_Query $query The WP_Query instance (passed by reference).
3629 */
3630 $this->found_posts = (int) apply_filters_ref_array( 'found_posts', array( $this->found_posts, &$this ) );
3631
3632 if ( ! empty( $limits ) ) {
3633 $this->max_num_pages = ceil( $this->found_posts / $q['posts_per_page'] );
3634 }
3635 }
3636
3637 /**
3638 * Sets up the next post and iterate current post index.
3639 *
3640 * @since 1.5.0
3641 *
3642 * @return WP_Post Next post.
3643 */
3644 public function next_post() {
3645
3646 ++$this->current_post;
3647
3648 /** @var WP_Post */
3649 $this->post = $this->posts[ $this->current_post ];
3650 return $this->post;
3651 }
3652
3653 /**
3654 * Sets up the current post.
3655 *
3656 * Retrieves the next post, sets up the post, sets the 'in the loop'
3657 * property to true.
3658 *
3659 * @since 1.5.0
3660 *
3661 * @global WP_Post $post Global post object.
3662 */
3663 public function the_post() {
3664 global $post;
3665
3666 if ( ! $this->in_the_loop ) {
3667 // Only prime the post cache for queries limited to the ID field.
3668 $post_ids = array_filter( $this->posts, 'is_numeric' );
3669 // Exclude any falsey values, such as 0.
3670 $post_ids = array_filter( $post_ids );
3671 if ( $post_ids ) {
3672 _prime_post_caches( $post_ids, $this->query_vars['update_post_term_cache'], $this->query_vars['update_post_meta_cache'] );
3673 }
3674 $post_objects = array_map( 'get_post', $this->posts );
3675 update_post_author_caches( $post_objects );
3676 }
3677
3678 $this->in_the_loop = true;
3679 $this->before_loop = false;
3680
3681 if ( -1 == $this->current_post ) { // Loop has just started.
3682 /**
3683 * Fires once the loop is started.
3684 *
3685 * @since 2.0.0
3686 *
3687 * @param WP_Query $query The WP_Query instance (passed by reference).
3688 */
3689 do_action_ref_array( 'loop_start', array( &$this ) );
3690 }
3691
3692 $post = $this->next_post();
3693 $this->setup_postdata( $post );
3694 }
3695
3696 /**
3697 * Determines whether there are more posts available in the loop.
3698 *
3699 * Calls the {@see 'loop_end'} action when the loop is complete.
3700 *
3701 * @since 1.5.0
3702 *
3703 * @return bool True if posts are available, false if end of the loop.
3704 */
3705 public function have_posts() {
3706 if ( $this->current_post + 1 < $this->post_count ) {
3707 return true;
3708 } elseif ( $this->current_post + 1 == $this->post_count && $this->post_count > 0 ) {
3709 /**
3710 * Fires once the loop has ended.
3711 *
3712 * @since 2.0.0
3713 *
3714 * @param WP_Query $query The WP_Query instance (passed by reference).
3715 */
3716 do_action_ref_array( 'loop_end', array( &$this ) );
3717 // Do some cleaning up after the loop.
3718 $this->rewind_posts();
3719 } elseif ( 0 === $this->post_count ) {
3720 $this->before_loop = false;
3721
3722 /**
3723 * Fires if no results are found in a post query.
3724 *
3725 * @since 4.9.0
3726 *
3727 * @param WP_Query $query The WP_Query instance.
3728 */
3729 do_action( 'loop_no_results', $this );
3730 }
3731
3732 $this->in_the_loop = false;
3733 return false;
3734 }
3735
3736 /**
3737 * Rewinds the posts and resets post index.
3738 *
3739 * @since 1.5.0
3740 */
3741 public function rewind_posts() {
3742 $this->current_post = -1;
3743 if ( $this->post_count > 0 ) {
3744 $this->post = $this->posts[0];
3745 }
3746 }
3747
3748 /**
3749 * Iterates current comment index and returns WP_Comment object.
3750 *
3751 * @since 2.2.0
3752 *
3753 * @return WP_Comment Comment object.
3754 */
3755 public function next_comment() {
3756 ++$this->current_comment;
3757
3758 /** @var WP_Comment */
3759 $this->comment = $this->comments[ $this->current_comment ];
3760 return $this->comment;
3761 }
3762
3763 /**
3764 * Sets up the current comment.
3765 *
3766 * @since 2.2.0
3767 *
3768 * @global WP_Comment $comment Global comment object.
3769 */
3770 public function the_comment() {
3771 global $comment;
3772
3773 $comment = $this->next_comment();
3774
3775 if ( 0 == $this->current_comment ) {
3776 /**
3777 * Fires once the comment loop is started.
3778 *
3779 * @since 2.2.0
3780 */
3781 do_action( 'comment_loop_start' );
3782 }
3783 }
3784
3785 /**
3786 * Determines whether there are more comments available.
3787 *
3788 * Automatically rewinds comments when finished.
3789 *
3790 * @since 2.2.0
3791 *
3792 * @return bool True if comments are available, false if no more comments.
3793 */
3794 public function have_comments() {
3795 if ( $this->current_comment + 1 < $this->comment_count ) {
3796 return true;
3797 } elseif ( $this->current_comment + 1 == $this->comment_count ) {
3798 $this->rewind_comments();
3799 }
3800
3801 return false;
3802 }
3803
3804 /**
3805 * Rewinds the comments, resets the comment index and comment to first.
3806 *
3807 * @since 2.2.0
3808 */
3809 public function rewind_comments() {
3810 $this->current_comment = -1;
3811 if ( $this->comment_count > 0 ) {
3812 $this->comment = $this->comments[0];
3813 }
3814 }
3815
3816 /**
3817 * Sets up the WordPress query by parsing query string.
3818 *
3819 * @since 1.5.0
3820 *
3821 * @see WP_Query::parse_query() for all available arguments.
3822 *
3823 * @param string|array $query URL query string or array of query arguments.
3824 * @return WP_Post[]|int[] Array of post objects or post IDs.
3825 */
3826 public function query( $query ) {
3827 $this->init();
3828 $this->query = wp_parse_args( $query );
3829 $this->query_vars = $this->query;
3830 return $this->get_posts();
3831 }
3832
3833 /**
3834 * Retrieves the currently queried object.
3835 *
3836 * If queried object is not set, then the queried object will be set from
3837 * the category, tag, taxonomy, posts page, single post, page, or author
3838 * query variable. After it is set up, it will be returned.
3839 *
3840 * @since 1.5.0
3841 *
3842 * @return WP_Term|WP_Post_Type|WP_Post|WP_User|null The queried object.
3843 */
3844 public function get_queried_object() {
3845 if ( isset( $this->queried_object ) ) {
3846 return $this->queried_object;
3847 }
3848
3849 $this->queried_object = null;
3850 $this->queried_object_id = null;
3851
3852 if ( $this->is_category || $this->is_tag || $this->is_tax ) {
3853 if ( $this->is_category ) {
3854 $cat = $this->get( 'cat' );
3855 $category_name = $this->get( 'category_name' );
3856
3857 if ( $cat ) {
3858 $term = get_term( $cat, 'category' );
3859 } elseif ( $category_name ) {
3860 $term = get_term_by( 'slug', $category_name, 'category' );
3861 }
3862 } elseif ( $this->is_tag ) {
3863 $tag_id = $this->get( 'tag_id' );
3864 $tag = $this->get( 'tag' );
3865
3866 if ( $tag_id ) {
3867 $term = get_term( $tag_id, 'post_tag' );
3868 } elseif ( $tag ) {
3869 $term = get_term_by( 'slug', $tag, 'post_tag' );
3870 }
3871 } else {
3872 // For other tax queries, grab the first term from the first clause.
3873 if ( ! empty( $this->tax_query->queried_terms ) ) {
3874 $queried_taxonomies = array_keys( $this->tax_query->queried_terms );
3875 $matched_taxonomy = reset( $queried_taxonomies );
3876 $query = $this->tax_query->queried_terms[ $matched_taxonomy ];
3877
3878 if ( ! empty( $query['terms'] ) ) {
3879 if ( 'term_id' === $query['field'] ) {
3880 $term = get_term( reset( $query['terms'] ), $matched_taxonomy );
3881 } else {
3882 $term = get_term_by( $query['field'], reset( $query['terms'] ), $matched_taxonomy );
3883 }
3884 }
3885 }
3886 }
3887
3888 if ( ! empty( $term ) && ! is_wp_error( $term ) ) {
3889 $this->queried_object = $term;
3890 $this->queried_object_id = (int) $term->term_id;
3891
3892 if ( $this->is_category && 'category' === $this->queried_object->taxonomy ) {
3893 _make_cat_compat( $this->queried_object );
3894 }
3895 }
3896 } elseif ( $this->is_post_type_archive ) {
3897 $post_type = $this->get( 'post_type' );
3898
3899 if ( is_array( $post_type ) ) {
3900 $post_type = reset( $post_type );
3901 }
3902
3903 $this->queried_object = get_post_type_object( $post_type );
3904 } elseif ( $this->is_posts_page ) {
3905 $page_for_posts = get_option( 'page_for_posts' );
3906
3907 $this->queried_object = get_post( $page_for_posts );
3908 $this->queried_object_id = (int) $this->queried_object->ID;
3909 } elseif ( $this->is_singular && ! empty( $this->post ) ) {
3910 $this->queried_object = $this->post;
3911 $this->queried_object_id = (int) $this->post->ID;
3912 } elseif ( $this->is_author ) {
3913 $author = (int) $this->get( 'author' );
3914 $author_name = $this->get( 'author_name' );
3915
3916 if ( $author ) {
3917 $this->queried_object_id = $author;
3918 } elseif ( $author_name ) {
3919 $user = get_user_by( 'slug', $author_name );
3920
3921 if ( $user ) {
3922 $this->queried_object_id = $user->ID;
3923 }
3924 }
3925
3926 $this->queried_object = get_userdata( $this->queried_object_id );
3927 }
3928
3929 return $this->queried_object;
3930 }
3931
3932 /**
3933 * Retrieves the ID of the currently queried object.
3934 *
3935 * @since 1.5.0
3936 *
3937 * @return int
3938 */
3939 public function get_queried_object_id() {
3940 $this->get_queried_object();
3941
3942 if ( isset( $this->queried_object_id ) ) {
3943 return $this->queried_object_id;
3944 }
3945
3946 return 0;
3947 }
3948
3949 /**
3950 * Constructor.
3951 *
3952 * Sets up the WordPress query, if parameter is not empty.
3953 *
3954 * @since 1.5.0
3955 *
3956 * @see WP_Query::parse_query() for all available arguments.
3957 *
3958 * @param string|array $query URL query string or array of vars.
3959 */
3960 public function __construct( $query = '' ) {
3961 if ( ! empty( $query ) ) {
3962 $this->query( $query );
3963 }
3964 }
3965
3966 /**
3967 * Makes private properties readable for backward compatibility.
3968 *
3969 * @since 4.0.0
3970 *
3971 * @param string $name Property to get.
3972 * @return mixed Property.
3973 */
3974 public function __get( $name ) {
3975 if ( in_array( $name, $this->compat_fields, true ) ) {
3976 return $this->$name;
3977 }
3978 }
3979
3980 /**
3981 * Makes private properties checkable for backward compatibility.
3982 *
3983 * @since 4.0.0
3984 *
3985 * @param string $name Property to check if set.
3986 * @return bool Whether the property is set.
3987 */
3988 public function __isset( $name ) {
3989 if ( in_array( $name, $this->compat_fields, true ) ) {
3990 return isset( $this->$name );
3991 }
3992 }
3993
3994 /**
3995 * Makes private/protected methods readable for backward compatibility.
3996 *
3997 * @since 4.0.0
3998 *
3999 * @param string $name Method to call.
4000 * @param array $arguments Arguments to pass when calling.
4001 * @return mixed|false Return value of the callback, false otherwise.
4002 */
4003 public function __call( $name, $arguments ) {
4004 if ( in_array( $name, $this->compat_methods, true ) ) {
4005 return $this->$name( ...$arguments );
4006 }
4007 return false;
4008 }
4009
4010 /**
4011 * Determines whether the query is for an existing archive page.
4012 *
4013 * Archive pages include category, tag, author, date, custom post type,
4014 * and custom taxonomy based archives.
4015 *
4016 * @since 3.1.0
4017 *
4018 * @see WP_Query::is_category()
4019 * @see WP_Query::is_tag()
4020 * @see WP_Query::is_author()
4021 * @see WP_Query::is_date()
4022 * @see WP_Query::is_post_type_archive()
4023 * @see WP_Query::is_tax()
4024 *
4025 * @return bool Whether the query is for an existing archive page.
4026 */
4027 public function is_archive() {
4028 return (bool) $this->is_archive;
4029 }
4030
4031 /**
4032 * Determines whether the query is for an existing post type archive page.
4033 *
4034 * @since 3.1.0
4035 *
4036 * @param string|string[] $post_types Optional. Post type or array of posts types
4037 * to check against. Default empty.
4038 * @return bool Whether the query is for an existing post type archive page.
4039 */
4040 public function is_post_type_archive( $post_types = '' ) {
4041 if ( empty( $post_types ) || ! $this->is_post_type_archive ) {
4042 return (bool) $this->is_post_type_archive;
4043 }
4044
4045 $post_type = $this->get( 'post_type' );
4046 if ( is_array( $post_type ) ) {
4047 $post_type = reset( $post_type );
4048 }
4049 $post_type_object = get_post_type_object( $post_type );
4050
4051 if ( ! $post_type_object ) {
4052 return false;
4053 }
4054
4055 return in_array( $post_type_object->name, (array) $post_types, true );
4056 }
4057
4058 /**
4059 * Determines whether the query is for an existing attachment page.
4060 *
4061 * @since 3.1.0
4062 *
4063 * @param int|string|int[]|string[] $attachment Optional. Attachment ID, title, slug, or array of such
4064 * to check against. Default empty.
4065 * @return bool Whether the query is for an existing attachment page.
4066 */
4067 public function is_attachment( $attachment = '' ) {
4068 if ( ! $this->is_attachment ) {
4069 return false;
4070 }
4071
4072 if ( empty( $attachment ) ) {
4073 return true;
4074 }
4075
4076 $attachment = array_map( 'strval', (array) $attachment );
4077
4078 $post_obj = $this->get_queried_object();
4079 if ( ! $post_obj ) {
4080 return false;
4081 }
4082
4083 if ( in_array( (string) $post_obj->ID, $attachment, true ) ) {
4084 return true;
4085 } elseif ( in_array( $post_obj->post_title, $attachment, true ) ) {
4086 return true;
4087 } elseif ( in_array( $post_obj->post_name, $attachment, true ) ) {
4088 return true;
4089 }
4090 return false;
4091 }
4092
4093 /**
4094 * Determines whether the query is for an existing author archive page.
4095 *
4096 * If the $author parameter is specified, this function will additionally
4097 * check if the query is for one of the authors specified.
4098 *
4099 * @since 3.1.0
4100 *
4101 * @param int|string|int[]|string[] $author Optional. User ID, nickname, nicename, or array of such
4102 * to check against. Default empty.
4103 * @return bool Whether the query is for an existing author archive page.
4104 */
4105 public function is_author( $author = '' ) {
4106 if ( ! $this->is_author ) {
4107 return false;
4108 }
4109
4110 if ( empty( $author ) ) {
4111 return true;
4112 }
4113
4114 $author_obj = $this->get_queried_object();
4115 if ( ! $author_obj ) {
4116 return false;
4117 }
4118
4119 $author = array_map( 'strval', (array) $author );
4120
4121 if ( in_array( (string) $author_obj->ID, $author, true ) ) {
4122 return true;
4123 } elseif ( in_array( $author_obj->nickname, $author, true ) ) {
4124 return true;
4125 } elseif ( in_array( $author_obj->user_nicename, $author, true ) ) {
4126 return true;
4127 }
4128
4129 return false;
4130 }
4131
4132 /**
4133 * Determines whether the query is for an existing category archive page.
4134 *
4135 * If the $category parameter is specified, this function will additionally
4136 * check if the query is for one of the categories specified.
4137 *
4138 * @since 3.1.0
4139 *
4140 * @param int|string|int[]|string[] $category Optional. Category ID, name, slug, or array of such
4141 * to check against. Default empty.
4142 * @return bool Whether the query is for an existing category archive page.
4143 */
4144 public function is_category( $category = '' ) {
4145 if ( ! $this->is_category ) {
4146 return false;
4147 }
4148
4149 if ( empty( $category ) ) {
4150 return true;
4151 }
4152
4153 $cat_obj = $this->get_queried_object();
4154 if ( ! $cat_obj ) {
4155 return false;
4156 }
4157
4158 $category = array_map( 'strval', (array) $category );
4159
4160 if ( in_array( (string) $cat_obj->term_id, $category, true ) ) {
4161 return true;
4162 } elseif ( in_array( $cat_obj->name, $category, true ) ) {
4163 return true;
4164 } elseif ( in_array( $cat_obj->slug, $category, true ) ) {
4165 return true;
4166 }
4167
4168 return false;
4169 }
4170
4171 /**
4172 * Determines whether the query is for an existing tag archive page.
4173 *
4174 * If the $tag parameter is specified, this function will additionally
4175 * check if the query is for one of the tags specified.
4176 *
4177 * @since 3.1.0
4178 *
4179 * @param int|string|int[]|string[] $tag Optional. Tag ID, name, slug, or array of such
4180 * to check against. Default empty.
4181 * @return bool Whether the query is for an existing tag archive page.
4182 */
4183 public function is_tag( $tag = '' ) {
4184 if ( ! $this->is_tag ) {
4185 return false;
4186 }
4187
4188 if ( empty( $tag ) ) {
4189 return true;
4190 }
4191
4192 $tag_obj = $this->get_queried_object();
4193 if ( ! $tag_obj ) {
4194 return false;
4195 }
4196
4197 $tag = array_map( 'strval', (array) $tag );
4198
4199 if ( in_array( (string) $tag_obj->term_id, $tag, true ) ) {
4200 return true;
4201 } elseif ( in_array( $tag_obj->name, $tag, true ) ) {
4202 return true;
4203 } elseif ( in_array( $tag_obj->slug, $tag, true ) ) {
4204 return true;
4205 }
4206
4207 return false;
4208 }
4209
4210 /**
4211 * Determines whether the query is for an existing custom taxonomy archive page.
4212 *
4213 * If the $taxonomy parameter is specified, this function will additionally
4214 * check if the query is for that specific $taxonomy.
4215 *
4216 * If the $term parameter is specified in addition to the $taxonomy parameter,
4217 * this function will additionally check if the query is for one of the terms
4218 * specified.
4219 *
4220 * @since 3.1.0
4221 *
4222 * @global WP_Taxonomy[] $wp_taxonomies Registered taxonomies.
4223 *
4224 * @param string|string[] $taxonomy Optional. Taxonomy slug or slugs to check against.
4225 * Default empty.
4226 * @param int|string|int[]|string[] $term Optional. Term ID, name, slug, or array of such
4227 * to check against. Default empty.
4228 * @return bool Whether the query is for an existing custom taxonomy archive page.
4229 * True for custom taxonomy archive pages, false for built-in taxonomies
4230 * (category and tag archives).
4231 */
4232 public function is_tax( $taxonomy = '', $term = '' ) {
4233 global $wp_taxonomies;
4234
4235 if ( ! $this->is_tax ) {
4236 return false;
4237 }
4238
4239 if ( empty( $taxonomy ) ) {
4240 return true;
4241 }
4242
4243 $queried_object = $this->get_queried_object();
4244 $tax_array = array_intersect( array_keys( $wp_taxonomies ), (array) $taxonomy );
4245 $term_array = (array) $term;
4246
4247 // Check that the taxonomy matches.
4248 if ( ! ( isset( $queried_object->taxonomy ) && count( $tax_array ) && in_array( $queried_object->taxonomy, $tax_array, true ) ) ) {
4249 return false;
4250 }
4251
4252 // Only a taxonomy provided.
4253 if ( empty( $term ) ) {
4254 return true;
4255 }
4256
4257 return isset( $queried_object->term_id ) &&
4258 count(
4259 array_intersect(
4260 array( $queried_object->term_id, $queried_object->name, $queried_object->slug ),
4261 $term_array
4262 )
4263 );
4264 }
4265
4266 /**
4267 * Determines whether the current URL is within the comments popup window.
4268 *
4269 * @since 3.1.0
4270 * @deprecated 4.5.0
4271 *
4272 * @return false Always returns false.
4273 */
4274 public function is_comments_popup() {
4275 _deprecated_function( __FUNCTION__, '4.5.0' );
4276
4277 return false;
4278 }
4279
4280 /**
4281 * Determines whether the query is for an existing date archive.
4282 *
4283 * @since 3.1.0
4284 *
4285 * @return bool Whether the query is for an existing date archive.
4286 */
4287 public function is_date() {
4288 return (bool) $this->is_date;
4289 }
4290
4291 /**
4292 * Determines whether the query is for an existing day archive.
4293 *
4294 * @since 3.1.0
4295 *
4296 * @return bool Whether the query is for an existing day archive.
4297 */
4298 public function is_day() {
4299 return (bool) $this->is_day;
4300 }
4301
4302 /**
4303 * Determines whether the query is for a feed.
4304 *
4305 * @since 3.1.0
4306 *
4307 * @param string|string[] $feeds Optional. Feed type or array of feed types
4308 * to check against. Default empty.
4309 * @return bool Whether the query is for a feed.
4310 */
4311 public function is_feed( $feeds = '' ) {
4312 if ( empty( $feeds ) || ! $this->is_feed ) {
4313 return (bool) $this->is_feed;
4314 }
4315
4316 $qv = $this->get( 'feed' );
4317 if ( 'feed' === $qv ) {
4318 $qv = get_default_feed();
4319 }
4320
4321 return in_array( $qv, (array) $feeds, true );
4322 }
4323
4324 /**
4325 * Determines whether the query is for a comments feed.
4326 *
4327 * @since 3.1.0
4328 *
4329 * @return bool Whether the query is for a comments feed.
4330 */
4331 public function is_comment_feed() {
4332 return (bool) $this->is_comment_feed;
4333 }
4334
4335 /**
4336 * Determines whether the query is for the front page of the site.
4337 *
4338 * This is for what is displayed at your site's main URL.
4339 *
4340 * Depends on the site's "Front page displays" Reading Settings 'show_on_front' and 'page_on_front'.
4341 *
4342 * If you set a static page for the front page of your site, this function will return
4343 * true when viewing that page.
4344 *
4345 * Otherwise the same as {@see WP_Query::is_home()}.
4346 *
4347 * @since 3.1.0
4348 *
4349 * @return bool Whether the query is for the front page of the site.
4350 */
4351 public function is_front_page() {
4352 // Most likely case.
4353 if ( 'posts' === get_option( 'show_on_front' ) && $this->is_home() ) {
4354 return true;
4355 } elseif ( 'page' === get_option( 'show_on_front' ) && get_option( 'page_on_front' )
4356 && $this->is_page( get_option( 'page_on_front' ) )
4357 ) {
4358 return true;
4359 } else {
4360 return false;
4361 }
4362 }
4363
4364 /**
4365 * Determines whether the query is for the blog homepage.
4366 *
4367 * This is the page which shows the time based blog content of your site.
4368 *
4369 * Depends on the site's "Front page displays" Reading Settings 'show_on_front' and 'page_for_posts'.
4370 *
4371 * If you set a static page for the front page of your site, this function will return
4372 * true only on the page you set as the "Posts page".
4373 *
4374 * @since 3.1.0
4375 *
4376 * @see WP_Query::is_front_page()
4377 *
4378 * @return bool Whether the query is for the blog homepage.
4379 */
4380 public function is_home() {
4381 return (bool) $this->is_home;
4382 }
4383
4384 /**
4385 * Determines whether the query is for the Privacy Policy page.
4386 *
4387 * This is the page which shows the Privacy Policy content of your site.
4388 *
4389 * Depends on the site's "Change your Privacy Policy page" Privacy Settings 'wp_page_for_privacy_policy'.
4390 *
4391 * This function will return true only on the page you set as the "Privacy Policy page".
4392 *
4393 * @since 5.2.0
4394 *
4395 * @return bool Whether the query is for the Privacy Policy page.
4396 */
4397 public function is_privacy_policy() {
4398 if ( get_option( 'wp_page_for_privacy_policy' )
4399 && $this->is_page( get_option( 'wp_page_for_privacy_policy' ) )
4400 ) {
4401 return true;
4402 } else {
4403 return false;
4404 }
4405 }
4406
4407 /**
4408 * Determines whether the query is for an existing month archive.
4409 *
4410 * @since 3.1.0
4411 *
4412 * @return bool Whether the query is for an existing month archive.
4413 */
4414 public function is_month() {
4415 return (bool) $this->is_month;
4416 }
4417
4418 /**
4419 * Determines whether the query is for an existing single page.
4420 *
4421 * If the $page parameter is specified, this function will additionally
4422 * check if the query is for one of the pages specified.
4423 *
4424 * @since 3.1.0
4425 *
4426 * @see WP_Query::is_single()
4427 * @see WP_Query::is_singular()
4428 *
4429 * @param int|string|int[]|string[] $page Optional. Page ID, title, slug, path, or array of such
4430 * to check against. Default empty.
4431 * @return bool Whether the query is for an existing single page.
4432 */
4433 public function is_page( $page = '' ) {
4434 if ( ! $this->is_page ) {
4435 return false;
4436 }
4437
4438 if ( empty( $page ) ) {
4439 return true;
4440 }
4441
4442 $page_obj = $this->get_queried_object();
4443 if ( ! $page_obj ) {
4444 return false;
4445 }
4446
4447 $page = array_map( 'strval', (array) $page );
4448
4449 if ( in_array( (string) $page_obj->ID, $page, true ) ) {
4450 return true;
4451 } elseif ( in_array( $page_obj->post_title, $page, true ) ) {
4452 return true;
4453 } elseif ( in_array( $page_obj->post_name, $page, true ) ) {
4454 return true;
4455 } else {
4456 foreach ( $page as $pagepath ) {
4457 if ( ! strpos( $pagepath, '/' ) ) {
4458 continue;
4459 }
4460 $pagepath_obj = get_page_by_path( $pagepath );
4461
4462 if ( $pagepath_obj && ( $pagepath_obj->ID == $page_obj->ID ) ) {
4463 return true;
4464 }
4465 }
4466 }
4467
4468 return false;
4469 }
4470
4471 /**
4472 * Determines whether the query is for a paged result and not for the first page.
4473 *
4474 * @since 3.1.0
4475 *
4476 * @return bool Whether the query is for a paged result.
4477 */
4478 public function is_paged() {
4479 return (bool) $this->is_paged;
4480 }
4481
4482 /**
4483 * Determines whether the query is for a post or page preview.
4484 *
4485 * @since 3.1.0
4486 *
4487 * @return bool Whether the query is for a post or page preview.
4488 */
4489 public function is_preview() {
4490 return (bool) $this->is_preview;
4491 }
4492
4493 /**
4494 * Determines whether the query is for the robots.txt file.
4495 *
4496 * @since 3.1.0
4497 *
4498 * @return bool Whether the query is for the robots.txt file.
4499 */
4500 public function is_robots() {
4501 return (bool) $this->is_robots;
4502 }
4503
4504 /**
4505 * Determines whether the query is for the favicon.ico file.
4506 *
4507 * @since 5.4.0
4508 *
4509 * @return bool Whether the query is for the favicon.ico file.
4510 */
4511 public function is_favicon() {
4512 return (bool) $this->is_favicon;
4513 }
4514
4515 /**
4516 * Determines whether the query is for a search.
4517 *
4518 * @since 3.1.0
4519 *
4520 * @return bool Whether the query is for a search.
4521 */
4522 public function is_search() {
4523 return (bool) $this->is_search;
4524 }
4525
4526 /**
4527 * Determines whether the query is for an existing single post.
4528 *
4529 * Works for any post type excluding pages.
4530 *
4531 * If the $post parameter is specified, this function will additionally
4532 * check if the query is for one of the Posts specified.
4533 *
4534 * @since 3.1.0
4535 *
4536 * @see WP_Query::is_page()
4537 * @see WP_Query::is_singular()
4538 *
4539 * @param int|string|int[]|string[] $post Optional. Post ID, title, slug, path, or array of such
4540 * to check against. Default empty.
4541 * @return bool Whether the query is for an existing single post.
4542 */
4543 public function is_single( $post = '' ) {
4544 if ( ! $this->is_single ) {
4545 return false;
4546 }
4547
4548 if ( empty( $post ) ) {
4549 return true;
4550 }
4551
4552 $post_obj = $this->get_queried_object();
4553 if ( ! $post_obj ) {
4554 return false;
4555 }
4556
4557 $post = array_map( 'strval', (array) $post );
4558
4559 if ( in_array( (string) $post_obj->ID, $post, true ) ) {
4560 return true;
4561 } elseif ( in_array( $post_obj->post_title, $post, true ) ) {
4562 return true;
4563 } elseif ( in_array( $post_obj->post_name, $post, true ) ) {
4564 return true;
4565 } else {
4566 foreach ( $post as $postpath ) {
4567 if ( ! strpos( $postpath, '/' ) ) {
4568 continue;
4569 }
4570 $postpath_obj = get_page_by_path( $postpath, OBJECT, $post_obj->post_type );
4571
4572 if ( $postpath_obj && ( $postpath_obj->ID == $post_obj->ID ) ) {
4573 return true;
4574 }
4575 }
4576 }
4577 return false;
4578 }
4579
4580 /**
4581 * Determines whether the query is for an existing single post of any post type
4582 * (post, attachment, page, custom post types).
4583 *
4584 * If the $post_types parameter is specified, this function will additionally
4585 * check if the query is for one of the Posts Types specified.
4586 *
4587 * @since 3.1.0
4588 *
4589 * @see WP_Query::is_page()
4590 * @see WP_Query::is_single()
4591 *
4592 * @param string|string[] $post_types Optional. Post type or array of post types
4593 * to check against. Default empty.
4594 * @return bool Whether the query is for an existing single post
4595 * or any of the given post types.
4596 */
4597 public function is_singular( $post_types = '' ) {
4598 if ( empty( $post_types ) || ! $this->is_singular ) {
4599 return (bool) $this->is_singular;
4600 }
4601
4602 $post_obj = $this->get_queried_object();
4603 if ( ! $post_obj ) {
4604 return false;
4605 }
4606
4607 return in_array( $post_obj->post_type, (array) $post_types, true );
4608 }
4609
4610 /**
4611 * Determines whether the query is for a specific time.
4612 *
4613 * @since 3.1.0
4614 *
4615 * @return bool Whether the query is for a specific time.
4616 */
4617 public function is_time() {
4618 return (bool) $this->is_time;
4619 }
4620
4621 /**
4622 * Determines whether the query is for a trackback endpoint call.
4623 *
4624 * @since 3.1.0
4625 *
4626 * @return bool Whether the query is for a trackback endpoint call.
4627 */
4628 public function is_trackback() {
4629 return (bool) $this->is_trackback;
4630 }
4631
4632 /**
4633 * Determines whether the query is for an existing year archive.
4634 *
4635 * @since 3.1.0
4636 *
4637 * @return bool Whether the query is for an existing year archive.
4638 */
4639 public function is_year() {
4640 return (bool) $this->is_year;
4641 }
4642
4643 /**
4644 * Determines whether the query is a 404 (returns no results).
4645 *
4646 * @since 3.1.0
4647 *
4648 * @return bool Whether the query is a 404 error.
4649 */
4650 public function is_404() {
4651 return (bool) $this->is_404;
4652 }
4653
4654 /**
4655 * Determines whether the query is for an embedded post.
4656 *
4657 * @since 4.4.0
4658 *
4659 * @return bool Whether the query is for an embedded post.
4660 */
4661 public function is_embed() {
4662 return (bool) $this->is_embed;
4663 }
4664
4665 /**
4666 * Determines whether the query is the main query.
4667 *
4668 * @since 3.3.0
4669 *
4670 * @global WP_Query $wp_query WordPress Query object.
4671 *
4672 * @return bool Whether the query is the main query.
4673 */
4674 public function is_main_query() {
4675 global $wp_the_query;
4676 return $wp_the_query === $this;
4677 }
4678
4679 /**
4680 * Sets up global post data.
4681 *
4682 * @since 4.1.0
4683 * @since 4.4.0 Added the ability to pass a post ID to `$post`.
4684 *
4685 * @global int $id
4686 * @global WP_User $authordata
4687 * @global string $currentday
4688 * @global string $currentmonth
4689 * @global int $page
4690 * @global array $pages
4691 * @global int $multipage
4692 * @global int $more
4693 * @global int $numpages
4694 *
4695 * @param WP_Post|object|int $post WP_Post instance or Post ID/object.
4696 * @return true True when finished.
4697 */
4698 public function setup_postdata( $post ) {
4699 global $id, $authordata, $currentday, $currentmonth, $page, $pages, $multipage, $more, $numpages;
4700
4701 if ( ! ( $post instanceof WP_Post ) ) {
4702 $post = get_post( $post );
4703 }
4704
4705 if ( ! $post ) {
4706 return;
4707 }
4708
4709 $elements = $this->generate_postdata( $post );
4710 if ( false === $elements ) {
4711 return;
4712 }
4713
4714 $id = $elements['id'];
4715 $authordata = $elements['authordata'];
4716 $currentday = $elements['currentday'];
4717 $currentmonth = $elements['currentmonth'];
4718 $page = $elements['page'];
4719 $pages = $elements['pages'];
4720 $multipage = $elements['multipage'];
4721 $more = $elements['more'];
4722 $numpages = $elements['numpages'];
4723
4724 /**
4725 * Fires once the post data has been set up.
4726 *
4727 * @since 2.8.0
4728 * @since 4.1.0 Introduced `$query` parameter.
4729 *
4730 * @param WP_Post $post The Post object (passed by reference).
4731 * @param WP_Query $query The current Query object (passed by reference).
4732 */
4733 do_action_ref_array( 'the_post', array( &$post, &$this ) );
4734
4735 return true;
4736 }
4737
4738 /**
4739 * Generates post data.
4740 *
4741 * @since 5.2.0
4742 *
4743 * @param WP_Post|object|int $post WP_Post instance or Post ID/object.
4744 * @return array|false Elements of post or false on failure.
4745 */
4746 public function generate_postdata( $post ) {
4747
4748 if ( ! ( $post instanceof WP_Post ) ) {
4749 $post = get_post( $post );
4750 }
4751
4752 if ( ! $post ) {
4753 return false;
4754 }
4755
4756 $id = (int) $post->ID;
4757
4758 $authordata = get_userdata( $post->post_author );
4759
4760 $currentday = false;
4761 $currentmonth = false;
4762
4763 $post_date = $post->post_date;
4764 if ( ! empty( $post_date ) && '0000-00-00 00:00:00' !== $post_date ) {
4765 // Avoid using mysql2date for performance reasons.
4766 $currentmonth = substr( $post_date, 5, 2 );
4767 $day = substr( $post_date, 8, 2 );
4768 $year = substr( $post_date, 2, 2 );
4769
4770 $currentday = sprintf( '%s.%s.%s', $day, $currentmonth, $year );
4771 }
4772
4773 $numpages = 1;
4774 $multipage = 0;
4775 $page = $this->get( 'page' );
4776 if ( ! $page ) {
4777 $page = 1;
4778 }
4779
4780 /*
4781 * Force full post content when viewing the permalink for the $post,
4782 * or when on an RSS feed. Otherwise respect the 'more' tag.
4783 */
4784 if ( get_queried_object_id() === $post->ID && ( $this->is_page() || $this->is_single() ) ) {
4785 $more = 1;
4786 } elseif ( $this->is_feed() ) {
4787 $more = 1;
4788 } else {
4789 $more = 0;
4790 }
4791
4792 $content = $post->post_content;
4793 if ( str_contains( $content, '<!--nextpage-->' ) ) {
4794 $content = str_replace( "\n<!--nextpage-->\n", '<!--nextpage-->', $content );
4795 $content = str_replace( "\n<!--nextpage-->", '<!--nextpage-->', $content );
4796 $content = str_replace( "<!--nextpage-->\n", '<!--nextpage-->', $content );
4797
4798 // Remove the nextpage block delimiters, to avoid invalid block structures in the split content.
4799 $content = str_replace( '<!-- wp:nextpage -->', '', $content );
4800 $content = str_replace( '<!-- /wp:nextpage -->', '', $content );
4801
4802 // Ignore nextpage at the beginning of the content.
4803 if ( str_starts_with( $content, '<!--nextpage-->' ) ) {
4804 $content = substr( $content, 15 );
4805 }
4806
4807 $pages = explode( '<!--nextpage-->', $content );
4808 } else {
4809 $pages = array( $post->post_content );
4810 }
4811
4812 /**
4813 * Filters the "pages" derived from splitting the post content.
4814 *
4815 * "Pages" are determined by splitting the post content based on the presence
4816 * of `<!-- nextpage -->` tags.
4817 *
4818 * @since 4.4.0
4819 *
4820 * @param string[] $pages Array of "pages" from the post content split by `<!-- nextpage -->` tags.
4821 * @param WP_Post $post Current post object.
4822 */
4823 $pages = apply_filters( 'content_pagination', $pages, $post );
4824
4825 $numpages = count( $pages );
4826
4827 if ( $numpages > 1 ) {
4828 if ( $page > 1 ) {
4829 $more = 1;
4830 }
4831 $multipage = 1;
4832 } else {
4833 $multipage = 0;
4834 }
4835
4836 $elements = compact( 'id', 'authordata', 'currentday', 'currentmonth', 'page', 'pages', 'multipage', 'more', 'numpages' );
4837
4838 return $elements;
4839 }
4840
4841 /**
4842 * Generates cache key.
4843 *
4844 * @since 6.1.0
4845 *
4846 * @global wpdb $wpdb WordPress database abstraction object.
4847 *
4848 * @param array $args Query arguments.
4849 * @param string $sql SQL statement.
4850 * @return string Cache key.
4851 */
4852 protected function generate_cache_key( array $args, $sql ) {
4853 global $wpdb;
4854
4855 unset(
4856 $args['cache_results'],
4857 $args['fields'],
4858 $args['lazy_load_term_meta'],
4859 $args['update_post_meta_cache'],
4860 $args['update_post_term_cache'],
4861 $args['update_menu_item_cache'],
4862 $args['suppress_filters']
4863 );
4864
4865 $placeholder = $wpdb->placeholder_escape();
4866 array_walk_recursive(
4867 $args,
4868 /*
4869 * Replace wpdb placeholders with the string used in the database
4870 * query to avoid unreachable cache keys. This is necessary because
4871 * the placeholder is randomly generated in each request.
4872 *
4873 * $value is passed by reference to allow it to be modified.
4874 * array_walk_recursive() does not return an array.
4875 */
4876 static function ( &$value ) use ( $wpdb, $placeholder ) {
4877 if ( is_string( $value ) && str_contains( $value, $placeholder ) ) {
4878 $value = $wpdb->remove_placeholder_escape( $value );
4879 }
4880 }
4881 );
4882
4883 // Replace wpdb placeholder in the SQL statement used by the cache key.
4884 $sql = $wpdb->remove_placeholder_escape( $sql );
4885 $key = md5( serialize( $args ) . $sql );
4886
4887 $last_changed = wp_cache_get_last_changed( 'posts' );
4888 if ( ! empty( $this->tax_query->queries ) ) {
4889 $last_changed .= wp_cache_get_last_changed( 'terms' );
4890 }
4891
4892 return "wp_query:$key:$last_changed";
4893 }
4894
4895 /**
4896 * After looping through a nested query, this function
4897 * restores the $post global to the current post in this query.
4898 *
4899 * @since 3.7.0
4900 *
4901 * @global WP_Post $post Global post object.
4902 */
4903 public function reset_postdata() {
4904 if ( ! empty( $this->post ) ) {
4905 $GLOBALS['post'] = $this->post;
4906 $this->setup_postdata( $this->post );
4907 }
4908 }
4909
4910 /**
4911 * Lazyloads term meta for posts in the loop.
4912 *
4913 * @since 4.4.0
4914 * @deprecated 4.5.0 See wp_queue_posts_for_term_meta_lazyload().
4915 *
4916 * @param mixed $check
4917 * @param int $term_id
4918 * @return mixed
4919 */
4920 public function lazyload_term_meta( $check, $term_id ) {
4921 _deprecated_function( __METHOD__, '4.5.0' );
4922 return $check;
4923 }
4924
4925 /**
4926 * Lazyloads comment meta for comments in the loop.
4927 *
4928 * @since 4.4.0
4929 * @deprecated 4.5.0 See wp_lazyload_comment_meta().
4930 *
4931 * @param mixed $check
4932 * @param int $comment_id
4933 * @return mixed
4934 */
4935 public function lazyload_comment_meta( $check, $comment_id ) {
4936 _deprecated_function( __METHOD__, '4.5.0' );
4937 return $check;
4938 }
4939}