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