Make WordPress Core

Ticket #38709: class-wp-query.php

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

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

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