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


Generated : Wed Nov 11 08:20:02 2026 Cross-referenced by PHPXref