Make WordPress Core

Ticket #60490: class-wp-query.php

File class-wp-query.php, 148.3 KB (added by bburgay, 14 months 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}