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