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


Generated : Mon Oct 26 08:20:34 2026 Cross-referenced by PHPXref