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