[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/ -> class-wp-query.php (source)

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


Generated : Sat Dec 21 08:20:01 2024 Cross-referenced by PHPXref