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