diff -r 7b1b88e27a20 -r 48c4eec2b7e6 wp/wp-includes/class-wp-query.php --- a/wp/wp-includes/class-wp-query.php Thu Sep 29 08:06:27 2022 +0200 +++ b/wp/wp-includes/class-wp-query.php Fri Sep 05 18:40:08 2025 +0200 @@ -15,6 +15,7 @@ * @since 1.5.0 * @since 4.5.0 Removed the `$comments_popup` property. */ +#[AllowDynamicProperties] class WP_Query { /** @@ -37,7 +38,7 @@ * Taxonomy query, as passed to get_tax_sql(). * * @since 3.1.0 - * @var WP_Tax_Query A taxonomy query instance. + * @var WP_Tax_Query|null A taxonomy query instance. */ public $tax_query; @@ -108,6 +109,14 @@ public $current_post = -1; /** + * Whether the caller is before the loop. + * + * @since 6.3.0 + * @var bool + */ + public $before_loop = true; + + /** * Whether the loop has started and the caller is in the loop. * * @since 2.0.0 @@ -445,6 +454,14 @@ public $thumbnails_cached = false; /** + * Controls whether an attachment query should include filenames or not. + * + * @since 6.0.3 + * @var bool + */ + protected $allow_query_attachment_by_filename = false; + + /** * Cached list of search stopwords. * * @since 3.7.0 @@ -508,6 +525,7 @@ $this->post_count = 0; $this->current_post = -1; $this->in_the_loop = false; + $this->before_loop = true; unset( $this->request ); unset( $this->post ); unset( $this->comments ); @@ -522,7 +540,7 @@ } /** - * Reparse the query vars. + * Reparses the query vars. * * @since 1.5.0 */ @@ -601,6 +619,7 @@ 'post_parent__not_in', 'author__in', 'author__not_in', + 'search_columns', ); foreach ( $array_keys as $key ) { @@ -613,7 +632,7 @@ } /** - * Parse a query string and set query type booleans. + * Parses a query string and sets query type booleans. * * @since 1.5.0 * @since 4.2.0 Introduced the ability to order by specific clauses of a `$meta_query`, by passing the clause's @@ -627,141 +646,146 @@ * @since 4.9.0 Introduced the `$comment_count` parameter. * @since 5.1.0 Introduced the `$meta_compare_key` parameter. * @since 5.3.0 Introduced the `$meta_type_key` parameter. + * @since 6.1.0 Introduced the `$update_menu_item_cache` parameter. + * @since 6.2.0 Introduced the `$search_columns` parameter. * * @param string|array $query { * Optional. Array or string of Query parameters. * - * @type int $attachment_id Attachment post ID. Used for 'attachment' post_type. - * @type int|string $author Author ID, or comma-separated list of IDs. - * @type string $author_name User 'user_nicename'. - * @type int[] $author__in An array of author IDs to query from. - * @type int[] $author__not_in An array of author IDs not to query from. - * @type bool $cache_results Whether to cache post information. Default true. - * @type int|string $cat Category ID or comma-separated list of IDs (this or any children). - * @type int[] $category__and An array of category IDs (AND in). - * @type int[] $category__in An array of category IDs (OR in, no children). - * @type int[] $category__not_in An array of category IDs (NOT in). - * @type string $category_name Use category slug (not name, this or any children). - * @type array|int $comment_count Filter results by comment count. Provide an integer to match - * comment count exactly. Provide an array with integer 'value' - * and 'compare' operator ('=', '!=', '>', '>=', '<', '<=' ) to - * compare against comment_count in a specific way. - * @type string $comment_status Comment status. - * @type int $comments_per_page The number of comments to return per page. - * Default 'comments_per_page' option. - * @type array $date_query An associative array of WP_Date_Query arguments. - * See WP_Date_Query::__construct(). - * @type int $day Day of the month. Default empty. Accepts numbers 1-31. - * @type bool $exact Whether to search by exact keyword. Default false. - * @type string $fields Post fields to query for. Accepts: - * - '' Returns an array of complete post objects (`WP_Post[]`). - * - 'ids' Returns an array of post IDs (`int[]`). - * - 'id=>parent' Returns an associative array of parent post IDs, - * keyed by post ID (`int[]`). - * Default ''. - * @type int $hour Hour of the day. Default empty. Accepts numbers 0-23. - * @type int|bool $ignore_sticky_posts Whether to ignore sticky posts or not. Setting this to false - * excludes stickies from 'post__in'. Accepts 1|true, 0|false. - * Default false. - * @type int $m Combination YearMonth. Accepts any four-digit year and month - * numbers 1-12. Default empty. - * @type string|string[] $meta_key Meta key or keys to filter by. - * @type string|string[] $meta_value Meta value or values to filter by. - * @type string $meta_compare MySQL operator used for comparing the meta value. - * See WP_Meta_Query::__construct for accepted values and default value. - * @type string $meta_compare_key MySQL operator used for comparing the meta key. - * See WP_Meta_Query::__construct for accepted values and default value. - * @type string $meta_type MySQL data type that the meta_value column will be CAST to for comparisons. - * See WP_Meta_Query::__construct for accepted values and default value. - * @type string $meta_type_key MySQL data type that the meta_key column will be CAST to for comparisons. - * See WP_Meta_Query::__construct for accepted values and default value. - * @type array $meta_query An associative array of WP_Meta_Query arguments. - * See WP_Meta_Query::__construct for accepted values. - * @type int $menu_order The menu order of the posts. - * @type int $minute Minute of the hour. Default empty. Accepts numbers 0-59. - * @type int $monthnum The two-digit month. Default empty. Accepts numbers 1-12. - * @type string $name Post slug. - * @type bool $nopaging Show all posts (true) or paginate (false). Default false. - * @type bool $no_found_rows Whether to skip counting the total rows found. Enabling can improve - * performance. Default false. - * @type int $offset The number of posts to offset before retrieval. - * @type string $order Designates ascending or descending order of posts. Default 'DESC'. - * Accepts 'ASC', 'DESC'. - * @type string|array $orderby Sort retrieved posts by parameter. One or more options may be passed. - * To use 'meta_value', or 'meta_value_num', 'meta_key=keyname' must be - * also be defined. To sort by a specific `$meta_query` clause, use that - * clause's array key. Accepts: - * - 'none' - * - 'name' - * - 'author' - * - 'date' - * - 'title' - * - 'modified' - * - 'menu_order' - * - 'parent' - * - 'ID' - * - 'rand' - * - 'relevance' - * - 'RAND(x)' (where 'x' is an integer seed value) - * - 'comment_count' - * - 'meta_value' - * - 'meta_value_num' - * - 'post__in' - * - 'post_name__in' - * - 'post_parent__in' - * - The array keys of `$meta_query`. - * Default is 'date', except when a search is being performed, when - * the default is 'relevance'. - * @type int $p Post ID. - * @type int $page Show the number of posts that would show up on page X of a - * static front page. - * @type int $paged The number of the current page. - * @type int $page_id Page ID. - * @type string $pagename Page slug. - * @type string $perm Show posts if user has the appropriate capability. - * @type string $ping_status Ping status. - * @type int[] $post__in An array of post IDs to retrieve, sticky posts will be included. - * @type int[] $post__not_in An array of post IDs not to retrieve. Note: a string of comma- - * separated IDs will NOT work. - * @type string $post_mime_type The mime type of the post. Used for 'attachment' post_type. - * @type string[] $post_name__in An array of post slugs that results must match. - * @type int $post_parent Page ID to retrieve child pages for. Use 0 to only retrieve - * top-level pages. - * @type int[] $post_parent__in An array containing parent page IDs to query child pages from. - * @type int[] $post_parent__not_in An array containing parent page IDs not to query child pages from. - * @type string|string[] $post_type A post type slug (string) or array of post type slugs. - * Default 'any' if using 'tax_query'. - * @type string|string[] $post_status A post status (string) or array of post statuses. - * @type int $posts_per_page The number of posts to query for. Use -1 to request all posts. - * @type int $posts_per_archive_page The number of posts to query for by archive page. Overrides - * 'posts_per_page' when is_archive(), or is_search() are true. - * @type string $s Search keyword(s). Prepending a term with a hyphen will - * exclude posts matching that term. Eg, 'pillow -sofa' will - * return posts containing 'pillow' but not 'sofa'. The - * character used for exclusion can be modified using the - * the 'wp_query_search_exclusion_prefix' filter. - * @type int $second Second of the minute. Default empty. Accepts numbers 0-59. - * @type bool $sentence Whether to search by phrase. Default false. - * @type bool $suppress_filters Whether to suppress filters. Default false. - * @type string $tag Tag slug. Comma-separated (either), Plus-separated (all). - * @type int[] $tag__and An array of tag IDs (AND in). - * @type int[] $tag__in An array of tag IDs (OR in). - * @type int[] $tag__not_in An array of tag IDs (NOT in). - * @type int $tag_id Tag id or comma-separated list of IDs. - * @type string[] $tag_slug__and An array of tag slugs (AND in). - * @type string[] $tag_slug__in An array of tag slugs (OR in). unless 'ignore_sticky_posts' is - * true. Note: a string of comma-separated IDs will NOT work. - * @type array $tax_query An associative array of WP_Tax_Query arguments. - * See WP_Tax_Query->__construct(). - * @type string $title Post title. - * @type bool $update_post_meta_cache Whether to update the post meta cache. Default true. - * @type bool $update_post_term_cache Whether to update the post term cache. Default true. - * @type bool $lazy_load_term_meta Whether to lazy-load term meta. Setting to false will - * disable cache priming for term meta, so that each - * get_term_meta() call will hit the database. - * Defaults to the value of `$update_post_term_cache`. - * @type int $w The week number of the year. Default empty. Accepts numbers 0-53. - * @type int $year The four-digit year. Default empty. Accepts any four-digit year. + * @type int $attachment_id Attachment post ID. Used for 'attachment' post_type. + * @type int|string $author Author ID, or comma-separated list of IDs. + * @type string $author_name User 'user_nicename'. + * @type int[] $author__in An array of author IDs to query from. + * @type int[] $author__not_in An array of author IDs not to query from. + * @type bool $cache_results Whether to cache post information. Default true. + * @type int|string $cat Category ID or comma-separated list of IDs (this or any children). + * @type int[] $category__and An array of category IDs (AND in). + * @type int[] $category__in An array of category IDs (OR in, no children). + * @type int[] $category__not_in An array of category IDs (NOT in). + * @type string $category_name Use category slug (not name, this or any children). + * @type array|int $comment_count Filter results by comment count. Provide an integer to match + * comment count exactly. Provide an array with integer 'value' + * and 'compare' operator ('=', '!=', '>', '>=', '<', '<=' ) to + * compare against comment_count in a specific way. + * @type string $comment_status Comment status. + * @type int $comments_per_page The number of comments to return per page. + * Default 'comments_per_page' option. + * @type array $date_query An associative array of WP_Date_Query arguments. + * See WP_Date_Query::__construct(). + * @type int $day Day of the month. Default empty. Accepts numbers 1-31. + * @type bool $exact Whether to search by exact keyword. Default false. + * @type string $fields Post fields to query for. Accepts: + * - '' Returns an array of complete post objects (`WP_Post[]`). + * - 'ids' Returns an array of post IDs (`int[]`). + * - 'id=>parent' Returns an associative array of parent post IDs, + * keyed by post ID (`int[]`). + * Default ''. + * @type int $hour Hour of the day. Default empty. Accepts numbers 0-23. + * @type int|bool $ignore_sticky_posts Whether to ignore sticky posts or not. Setting this to false + * excludes stickies from 'post__in'. Accepts 1|true, 0|false. + * Default false. + * @type int $m Combination YearMonth. Accepts any four-digit year and month + * numbers 01-12. Default empty. + * @type string|string[] $meta_key Meta key or keys to filter by. + * @type string|string[] $meta_value Meta value or values to filter by. + * @type string $meta_compare MySQL operator used for comparing the meta value. + * See WP_Meta_Query::__construct() for accepted values and default value. + * @type string $meta_compare_key MySQL operator used for comparing the meta key. + * See WP_Meta_Query::__construct() for accepted values and default value. + * @type string $meta_type MySQL data type that the meta_value column will be CAST to for comparisons. + * See WP_Meta_Query::__construct() for accepted values and default value. + * @type string $meta_type_key MySQL data type that the meta_key column will be CAST to for comparisons. + * See WP_Meta_Query::__construct() for accepted values and default value. + * @type array $meta_query An associative array of WP_Meta_Query arguments. + * See WP_Meta_Query::__construct() for accepted values. + * @type int $menu_order The menu order of the posts. + * @type int $minute Minute of the hour. Default empty. Accepts numbers 0-59. + * @type int $monthnum The two-digit month. Default empty. Accepts numbers 1-12. + * @type string $name Post slug. + * @type bool $nopaging Show all posts (true) or paginate (false). Default false. + * @type bool $no_found_rows Whether to skip counting the total rows found. Enabling can improve + * performance. Default false. + * @type int $offset The number of posts to offset before retrieval. + * @type string $order Designates ascending or descending order of posts. Default 'DESC'. + * Accepts 'ASC', 'DESC'. + * @type string|array $orderby Sort retrieved posts by parameter. One or more options may be passed. + * To use 'meta_value', or 'meta_value_num', 'meta_key=keyname' must be + * also be defined. To sort by a specific `$meta_query` clause, use that + * clause's array key. Accepts: + * - 'none' + * - 'name' + * - 'author' + * - 'date' + * - 'title' + * - 'modified' + * - 'menu_order' + * - 'parent' + * - 'ID' + * - 'rand' + * - 'relevance' + * - 'RAND(x)' (where 'x' is an integer seed value) + * - 'comment_count' + * - 'meta_value' + * - 'meta_value_num' + * - 'post__in' + * - 'post_name__in' + * - 'post_parent__in' + * - The array keys of `$meta_query`. + * Default is 'date', except when a search is being performed, when + * the default is 'relevance'. + * @type int $p Post ID. + * @type int $page Show the number of posts that would show up on page X of a + * static front page. + * @type int $paged The number of the current page. + * @type int $page_id Page ID. + * @type string $pagename Page slug. + * @type string $perm Show posts if user has the appropriate capability. + * @type string $ping_status Ping status. + * @type int[] $post__in An array of post IDs to retrieve, sticky posts will be included. + * @type int[] $post__not_in An array of post IDs not to retrieve. Note: a string of comma- + * separated IDs will NOT work. + * @type string $post_mime_type The mime type of the post. Used for 'attachment' post_type. + * @type string[] $post_name__in An array of post slugs that results must match. + * @type int $post_parent Page ID to retrieve child pages for. Use 0 to only retrieve + * top-level pages. + * @type int[] $post_parent__in An array containing parent page IDs to query child pages from. + * @type int[] $post_parent__not_in An array containing parent page IDs not to query child pages from. + * @type string|string[] $post_type A post type slug (string) or array of post type slugs. + * Default 'any' if using 'tax_query'. + * @type string|string[] $post_status A post status (string) or array of post statuses. + * @type int $posts_per_page The number of posts to query for. Use -1 to request all posts. + * @type int $posts_per_archive_page The number of posts to query for by archive page. Overrides + * 'posts_per_page' when is_archive(), or is_search() are true. + * @type string $s Search keyword(s). Prepending a term with a hyphen will + * exclude posts matching that term. Eg, 'pillow -sofa' will + * return posts containing 'pillow' but not 'sofa'. The + * character used for exclusion can be modified using the + * the 'wp_query_search_exclusion_prefix' filter. + * @type string[] $search_columns Array of column names to be searched. Accepts 'post_title', + * 'post_excerpt' and 'post_content'. Default empty array. + * @type int $second Second of the minute. Default empty. Accepts numbers 0-59. + * @type bool $sentence Whether to search by phrase. Default false. + * @type bool $suppress_filters Whether to suppress filters. Default false. + * @type string $tag Tag slug. Comma-separated (either), Plus-separated (all). + * @type int[] $tag__and An array of tag IDs (AND in). + * @type int[] $tag__in An array of tag IDs (OR in). + * @type int[] $tag__not_in An array of tag IDs (NOT in). + * @type int $tag_id Tag id or comma-separated list of IDs. + * @type string[] $tag_slug__and An array of tag slugs (AND in). + * @type string[] $tag_slug__in An array of tag slugs (OR in). unless 'ignore_sticky_posts' is + * true. Note: a string of comma-separated IDs will NOT work. + * @type array $tax_query An associative array of WP_Tax_Query arguments. + * See WP_Tax_Query::__construct(). + * @type string $title Post title. + * @type bool $update_post_meta_cache Whether to update the post meta cache. Default true. + * @type bool $update_post_term_cache Whether to update the post term cache. Default true. + * @type bool $update_menu_item_cache Whether to update the menu item cache. Default false. + * @type bool $lazy_load_term_meta Whether to lazy-load term meta. Setting to false will + * disable cache priming for term meta, so that each + * get_term_meta() call will hit the database. + * Defaults to the value of `$update_post_term_cache`. + * @type int $w The week number of the year. Default empty. Accepts numbers 0-53. + * @type int $year The four-digit year. Default empty. Accepts any four-digit year. * } */ public function parse_query( $query = '' ) { @@ -790,29 +814,41 @@ $qv['p'] = (int) $qv['p']; } - $qv['page_id'] = absint( $qv['page_id'] ); - $qv['year'] = absint( $qv['year'] ); - $qv['monthnum'] = absint( $qv['monthnum'] ); - $qv['day'] = absint( $qv['day'] ); - $qv['w'] = absint( $qv['w'] ); + $qv['page_id'] = is_scalar( $qv['page_id'] ) ? absint( $qv['page_id'] ) : 0; + $qv['year'] = is_scalar( $qv['year'] ) ? absint( $qv['year'] ) : 0; + $qv['monthnum'] = is_scalar( $qv['monthnum'] ) ? absint( $qv['monthnum'] ) : 0; + $qv['day'] = is_scalar( $qv['day'] ) ? absint( $qv['day'] ) : 0; + $qv['w'] = is_scalar( $qv['w'] ) ? absint( $qv['w'] ) : 0; $qv['m'] = is_scalar( $qv['m'] ) ? preg_replace( '|[^0-9]|', '', $qv['m'] ) : ''; - $qv['paged'] = absint( $qv['paged'] ); - $qv['cat'] = preg_replace( '|[^0-9,-]|', '', $qv['cat'] ); // Comma-separated list of positive or negative integers. - $qv['author'] = preg_replace( '|[^0-9,-]|', '', $qv['author'] ); // Comma-separated list of positive or negative integers. - $qv['pagename'] = trim( $qv['pagename'] ); - $qv['name'] = trim( $qv['name'] ); - $qv['title'] = trim( $qv['title'] ); - if ( '' !== $qv['hour'] ) { + $qv['paged'] = is_scalar( $qv['paged'] ) ? absint( $qv['paged'] ) : 0; + $qv['cat'] = preg_replace( '|[^0-9,-]|', '', $qv['cat'] ); // Array or comma-separated list of positive or negative integers. + $qv['author'] = is_scalar( $qv['author'] ) ? preg_replace( '|[^0-9,-]|', '', $qv['author'] ) : ''; // Comma-separated list of positive or negative integers. + $qv['pagename'] = is_scalar( $qv['pagename'] ) ? trim( $qv['pagename'] ) : ''; + $qv['name'] = is_scalar( $qv['name'] ) ? trim( $qv['name'] ) : ''; + $qv['title'] = is_scalar( $qv['title'] ) ? trim( $qv['title'] ) : ''; + + if ( is_scalar( $qv['hour'] ) && '' !== $qv['hour'] ) { $qv['hour'] = absint( $qv['hour'] ); - } - if ( '' !== $qv['minute'] ) { + } else { + $qv['hour'] = ''; + } + + if ( is_scalar( $qv['minute'] ) && '' !== $qv['minute'] ) { $qv['minute'] = absint( $qv['minute'] ); - } - if ( '' !== $qv['second'] ) { + } else { + $qv['minute'] = ''; + } + + if ( is_scalar( $qv['second'] ) && '' !== $qv['second'] ) { $qv['second'] = absint( $qv['second'] ); - } - if ( '' !== $qv['menu_order'] ) { + } else { + $qv['second'] = ''; + } + + if ( is_scalar( $qv['menu_order'] ) && '' !== $qv['menu_order'] ) { $qv['menu_order'] = absint( $qv['menu_order'] ); + } else { + $qv['menu_order'] = ''; } // Fairly large, potentially too large, upper bound for search string lengths. @@ -821,14 +857,14 @@ } // Compat. Map subpost to attachment. - if ( '' != $qv['subpost'] ) { + if ( is_scalar( $qv['subpost'] ) && '' != $qv['subpost'] ) { $qv['attachment'] = $qv['subpost']; } - if ( '' != $qv['subpost_id'] ) { + if ( is_scalar( $qv['subpost_id'] ) && '' != $qv['subpost_id'] ) { $qv['attachment_id'] = $qv['subpost_id']; } - $qv['attachment_id'] = absint( $qv['attachment_id'] ); + $qv['attachment_id'] = is_scalar( $qv['attachment_id'] ) ? absint( $qv['attachment_id'] ) : 0; if ( ( '' !== $qv['attachment'] ) || ! empty( $qv['attachment_id'] ) ) { $this->is_single = true; @@ -979,7 +1015,7 @@ $this->is_admin = true; } - if ( false !== strpos( $qv['feed'], 'comments-' ) ) { + if ( str_contains( $qv['feed'], 'comments-' ) ) { $qv['feed'] = str_replace( 'comments-', '', $qv['feed'] ); $qv['withcomments'] = 1; } @@ -991,7 +1027,7 @@ } if ( ! ( $this->is_singular || $this->is_archive || $this->is_search || $this->is_feed - || ( defined( 'REST_REQUEST' ) && REST_REQUEST && $this->is_main_query() ) + || ( wp_is_serving_rest_request() && $this->is_main_query() ) || $this->is_trackback || $this->is_404 || $this->is_admin || $this->is_robots || $this->is_favicon ) ) { $this->is_home = true; } @@ -1149,7 +1185,7 @@ $term = implode( ',', $term ); } - if ( strpos( $term, '+' ) !== false ) { + if ( str_contains( $term, '+' ) ) { $terms = preg_split( '/[+]+/', $term ); foreach ( $terms as $term ) { $tax_query[] = array_merge( @@ -1262,7 +1298,7 @@ // Tag stuff. if ( '' !== $q['tag'] && ! $this->is_singular && $this->query_vars_changed ) { - if ( strpos( $q['tag'], ',' ) !== false ) { + if ( str_contains( $q['tag'], ',' ) ) { $tags = preg_split( '/[,\r\n\t ]+/', $q['tag'] ); foreach ( (array) $tags as $tag ) { $tag = sanitize_term_field( 'slug', $tag, 0, 'post_tag', 'db' ); @@ -1387,6 +1423,32 @@ $searchand = ''; $q['search_orderby_title'] = array(); + $default_search_columns = array( 'post_title', 'post_excerpt', 'post_content' ); + $search_columns = ! empty( $q['search_columns'] ) ? $q['search_columns'] : $default_search_columns; + if ( ! is_array( $search_columns ) ) { + $search_columns = array( $search_columns ); + } + + /** + * Filters the columns to search in a WP_Query search. + * + * The supported columns are `post_title`, `post_excerpt` and `post_content`. + * They are all included by default. + * + * @since 6.2.0 + * + * @param string[] $search_columns Array of column names to be searched. + * @param string $search Text being searched. + * @param WP_Query $query The current WP_Query instance. + */ + $search_columns = (array) apply_filters( 'post_search_columns', $search_columns, $q['s'], $this ); + + // Use only supported search columns. + $search_columns = array_intersect( $search_columns, $default_search_columns ); + if ( empty( $search_columns ) ) { + $search_columns = $default_search_columns; + } + /** * Filters the prefix that indicates that a search term should be excluded from results. * @@ -1399,7 +1461,7 @@ foreach ( $q['search_terms'] as $term ) { // If there is an $exclusion_prefix, terms prefixed with it should be excluded. - $exclude = $exclusion_prefix && ( substr( $term, 0, 1 ) === $exclusion_prefix ); + $exclude = $exclusion_prefix && str_starts_with( $term, $exclusion_prefix ); if ( $exclude ) { $like_op = 'NOT LIKE'; $andor_op = 'AND'; @@ -1414,8 +1476,19 @@ $q['search_orderby_title'][] = $wpdb->prepare( "{$wpdb->posts}.post_title LIKE %s", $like ); } - $like = $n . $wpdb->esc_like( $term ) . $n; - $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 ); + $like = $n . $wpdb->esc_like( $term ) . $n; + + $search_columns_parts = array(); + foreach ( $search_columns as $search_column ) { + $search_columns_parts[ $search_column ] = $wpdb->prepare( "({$wpdb->posts}.$search_column $like_op %s)", $like ); + } + + if ( ! empty( $this->allow_query_attachment_by_filename ) ) { + $search_columns_parts['attachment'] = $wpdb->prepare( "(sq1.meta_value $like_op %s)", $like ); + } + + $search .= "$searchand(" . implode( " $andor_op ", $search_columns_parts ) . ')'; + $searchand = ' AND '; } @@ -1430,7 +1503,7 @@ } /** - * Check if the terms are suitable for searching. + * Checks if the terms are suitable for searching. * * Uses an array of stopwords (terms) that are excluded from the separate * term matching when searching for posts. The list of English stopwords is @@ -1471,7 +1544,7 @@ } /** - * Retrieve stopwords used when parsing search terms. + * Retrieves stopwords used when parsing search terms. * * @since 3.7.0 * @@ -1543,8 +1616,10 @@ $search_orderby .= $wpdb->prepare( "WHEN {$wpdb->posts}.post_title LIKE %s THEN 1 ", $like ); } - // Sanity limit, sort as sentence when more than 6 terms - // (few searches are longer than 6 terms and most titles are not). + /* + * Sanity limit, sort as sentence when more than 6 terms + * (few searches are longer than 6 terms and most titles are not). + */ if ( $num_terms < 7 ) { // All words in title. $search_orderby .= 'WHEN ' . implode( ' AND ', $q['search_orderby_title'] ) . ' THEN 2 '; @@ -1751,7 +1826,8 @@ * @since 3.9.0 The `$default_value` argument was introduced. * * @param string $query_var Query variable key. - * @param mixed $default_value Optional. Value to return if the query variable is not set. Default empty string. + * @param mixed $default_value Optional. Value to return if the query variable is not set. + * Default empty string. * @return mixed Contents of the query variable. */ public function get( $query_var, $default_value = '' ) { @@ -1810,6 +1886,16 @@ // Fill again in case 'pre_get_posts' unset some vars. $q = $this->fill_query_vars( $q ); + /** + * Filters whether an attachment query should include filenames or not. + * + * @since 6.0.3 + * + * @param bool $allow_query_attachment_by_filename Whether or not to include filenames. + */ + $this->allow_query_attachment_by_filename = apply_filters( 'wp_allow_query_attachment_by_filename', false ); + remove_all_filters( 'wp_allow_query_attachment_by_filename' ); + // Parse meta query. $this->meta_query = new WP_Meta_Query(); $this->meta_query->parse_query_vars( $q ); @@ -1860,19 +1946,21 @@ } if ( ! isset( $q['cache_results'] ) ) { - if ( wp_using_ext_object_cache() ) { - $q['cache_results'] = false; - } else { - $q['cache_results'] = true; - } + $q['cache_results'] = true; } if ( ! isset( $q['update_post_term_cache'] ) ) { $q['update_post_term_cache'] = true; } + if ( ! isset( $q['update_menu_item_cache'] ) ) { + $q['update_menu_item_cache'] = false; + } + if ( ! isset( $q['lazy_load_term_meta'] ) ) { $q['lazy_load_term_meta'] = $q['update_post_term_cache']; + } elseif ( $q['lazy_load_term_meta'] ) { // Lazy loading term meta only works if term caches are primed. + $q['update_post_term_cache'] = true; } if ( ! isset( $q['update_post_meta_cache'] ) ) { @@ -1932,8 +2020,7 @@ } if ( isset( $q['page'] ) ) { - $q['page'] = trim( $q['page'], '/' ); - $q['page'] = absint( $q['page'] ); + $q['page'] = is_scalar( $q['page'] ) ? absint( trim( $q['page'], '/' ) ) : 0; } // If true, forcibly turns off SQL_CALC_FOUND_ROWS even when limits are present. @@ -2175,8 +2262,11 @@ } if ( ! $post_type ) { $post_type = 'any'; - } elseif ( count( $post_type ) == 1 ) { + } elseif ( count( $post_type ) === 1 ) { $post_type = $post_type[0]; + } else { + // Sort post types to ensure same cache key generation. + sort( $post_type ); } $post_status_join = true; @@ -2241,7 +2331,7 @@ } } - if ( ! empty( $this->tax_query->queries ) || ! empty( $this->meta_query->queries ) ) { + if ( ! empty( $this->tax_query->queries ) || ! empty( $this->meta_query->queries ) || ! empty( $this->allow_query_attachment_by_filename ) ) { $groupby = "{$wpdb->posts}.ID"; } @@ -2268,7 +2358,7 @@ // Author stuff for nice URLs. if ( '' !== $q['author_name'] ) { - if ( strpos( $q['author_name'], '/' ) !== false ) { + if ( str_contains( $q['author_name'], '/' ) ) { $q['author_name'] = explode( '/', $q['author_name'] ); if ( $q['author_name'][ count( $q['author_name'] ) - 1 ] ) { $q['author_name'] = $q['author_name'][ count( $q['author_name'] ) - 1 ]; // No trailing slash. @@ -2318,6 +2408,10 @@ } $where .= $search . $whichauthor . $whichmimetype; + if ( ! empty( $this->allow_query_attachment_by_filename ) ) { + $join .= " LEFT JOIN {$wpdb->postmeta} AS sq1 ON ( {$wpdb->posts}.ID = sq1.post_id AND sq1.meta_key = '_wp_attached_file' )"; + } + if ( ! empty( $this->meta_query->queries ) ) { $clauses = $this->meta_query->get_sql( 'post', $wpdb->posts, 'ID', $this ); $join .= $clauses['join']; @@ -2451,6 +2545,8 @@ $post_type_where = " AND {$wpdb->posts}.post_type IN ('" . implode( "', '", array_map( 'esc_sql', $in_search_post_types ) ) . "')"; } } elseif ( ! empty( $post_type ) && is_array( $post_type ) ) { + // Sort post types to ensure same cache key generation. + sort( $post_type ); $post_type_where = " AND {$wpdb->posts}.post_type IN ('" . implode( "', '", esc_sql( $post_type ) ) . "')"; } elseif ( ! empty( $post_type ) ) { $post_type_where = $wpdb->prepare( " AND {$wpdb->posts}.post_type = %s", $post_type ); @@ -2556,7 +2652,7 @@ } if ( ! empty( $queried_post_types ) ) { - + sort( $queried_post_types ); $status_type_clauses = array(); foreach ( $queried_post_types as $queried_post_type ) { @@ -2727,12 +2823,12 @@ $last_changed = wp_cache_get_last_changed( 'comment' ) . ':' . wp_cache_get_last_changed( 'posts' ); $cache_key = "comment_feed:$key:$last_changed"; - $comment_ids = wp_cache_get( $cache_key, 'comment' ); + $comment_ids = wp_cache_get( $cache_key, 'comment-queries' ); if ( false === $comment_ids ) { $comment_ids = $wpdb->get_col( $comments_request ); - wp_cache_add( $cache_key, $comment_ids, 'comment' ); + wp_cache_add( $cache_key, $comment_ids, 'comment-queries' ); } - _prime_comment_caches( $comment_ids, false ); + _prime_comment_caches( $comment_ids ); // Convert to WP_Comment. /** @var WP_Comment[] */ @@ -3013,14 +3109,24 @@ $found_rows = 'SQL_CALC_FOUND_ROWS'; } - $old_request = " - SELECT $found_rows $distinct $fields - FROM {$wpdb->posts} $join - WHERE 1=1 $where - $groupby - $orderby - $limits - "; + /* + * Beginning of the string is on a new line to prevent leading whitespace. + * + * The additional indentation of subsequent lines is to ensure the SQL + * queries are identical to those generated when splitting queries. This + * improves caching of the query by ensuring the same cache key is + * generated for the same database queries functionally. + * + * See https://core.trac.wordpress.org/ticket/56841. + * See https://github.com/WordPress/wordpress-develop/pull/6393#issuecomment-2088217429 + */ + $old_request = + "SELECT $found_rows $distinct $fields + FROM {$wpdb->posts} $join + WHERE 1=1 $where + $groupby + $orderby + $limits"; $this->request = $old_request; @@ -3054,6 +3160,79 @@ */ $this->posts = apply_filters_ref_array( 'posts_pre_query', array( null, &$this ) ); + /* + * Ensure the ID database query is able to be cached. + * + * Random queries are expected to have unpredictable results and + * cannot be cached. Note the space before `RAND` in the string + * search, that to ensure against a collision with another + * function. + * + * If `$fields` has been modified by the `posts_fields`, + * `posts_fields_request`, `post_clauses` or `posts_clauses_request` + * filters, then caching is disabled to prevent caching collisions. + */ + $id_query_is_cacheable = ! str_contains( strtoupper( $orderby ), ' RAND(' ); + + $cacheable_field_values = array( + "{$wpdb->posts}.*", + "{$wpdb->posts}.ID, {$wpdb->posts}.post_parent", + "{$wpdb->posts}.ID", + ); + + if ( ! in_array( $fields, $cacheable_field_values, true ) ) { + $id_query_is_cacheable = false; + } + + if ( $q['cache_results'] && $id_query_is_cacheable ) { + $new_request = str_replace( $fields, "{$wpdb->posts}.*", $this->request ); + $cache_key = $this->generate_cache_key( $q, $new_request ); + + $cache_found = false; + if ( null === $this->posts ) { + $cached_results = wp_cache_get( $cache_key, 'post-queries', false, $cache_found ); + + if ( $cached_results ) { + /** @var int[] */ + $post_ids = array_map( 'intval', $cached_results['posts'] ); + + $this->post_count = count( $post_ids ); + $this->found_posts = $cached_results['found_posts']; + $this->max_num_pages = $cached_results['max_num_pages']; + + if ( 'ids' === $q['fields'] ) { + $this->posts = $post_ids; + + return $this->posts; + } elseif ( 'id=>parent' === $q['fields'] ) { + _prime_post_parent_id_caches( $post_ids ); + + $post_parent_cache_keys = array(); + foreach ( $post_ids as $post_id ) { + $post_parent_cache_keys[] = 'post_parent:' . (string) $post_id; + } + + /** @var int[] */ + $post_parents = wp_cache_get_multiple( $post_parent_cache_keys, 'posts' ); + + foreach ( $post_parents as $cache_key => $post_parent ) { + $obj = new stdClass(); + $obj->ID = (int) str_replace( 'post_parent:', '', $cache_key ); + $obj->post_parent = (int) $post_parent; + + $this->posts[] = $obj; + } + + return $post_parents; + } else { + _prime_post_caches( $post_ids, $q['update_post_term_cache'], $q['update_post_meta_cache'] ); + /** @var WP_Post[] */ + $this->posts = array_map( 'get_post', $post_ids ); + } + } + } + } + if ( 'ids' === $q['fields'] ) { if ( null === $this->posts ) { $this->posts = $wpdb->get_col( $this->request ); @@ -3064,6 +3243,16 @@ $this->post_count = count( $this->posts ); $this->set_found_posts( $q, $limits ); + if ( $q['cache_results'] && $id_query_is_cacheable ) { + $cache_value = array( + 'posts' => $this->posts, + 'found_posts' => $this->found_posts, + 'max_num_pages' => $this->max_num_pages, + ); + + wp_cache_set( $cache_key, $cache_value, 'post-queries' ); + } + return $this->posts; } @@ -3076,19 +3265,45 @@ $this->set_found_posts( $q, $limits ); /** @var int[] */ - $r = array(); + $post_parents = array(); + $post_ids = array(); + $post_parents_cache = array(); + foreach ( $this->posts as $key => $post ) { $this->posts[ $key ]->ID = (int) $post->ID; $this->posts[ $key ]->post_parent = (int) $post->post_parent; - $r[ (int) $post->ID ] = (int) $post->post_parent; + $post_parents[ (int) $post->ID ] = (int) $post->post_parent; + $post_ids[] = (int) $post->ID; + + $post_parents_cache[ 'post_parent:' . (string) $post->ID ] = (int) $post->post_parent; } - - return $r; - } + // Prime post parent caches, so that on second run, there is not another database query. + wp_cache_add_multiple( $post_parents_cache, 'posts' ); + + if ( $q['cache_results'] && $id_query_is_cacheable ) { + $cache_value = array( + 'posts' => $post_ids, + 'found_posts' => $this->found_posts, + 'max_num_pages' => $this->max_num_pages, + ); + + wp_cache_set( $cache_key, $cache_value, 'post-queries' ); + } + + return $post_parents; + } + + $is_unfiltered_query = $old_request == $this->request && "{$wpdb->posts}.*" === $fields; if ( null === $this->posts ) { - $split_the_query = ( $old_request == $this->request && "{$wpdb->posts}.*" === $fields && ! empty( $limits ) && $q['posts_per_page'] < 500 ); + $split_the_query = ( + $is_unfiltered_query + && ( + wp_using_ext_object_cache() + || ( ! empty( $limits ) && $q['posts_per_page'] < 500 ) + ) + ); /** * Filters whether to split the query. @@ -3098,23 +3313,36 @@ * complete row at once. One massive result vs. many small results. * * @since 3.4.0 + * @since 6.6.0 Added the `$old_request` and `$clauses` parameters. * * @param bool $split_the_query Whether or not to split the query. * @param WP_Query $query The WP_Query instance. + * @param string $old_request The complete SQL query before filtering. + * @param string[] $clauses { + * Associative array of the clauses for the query. + * + * @type string $where The WHERE clause of the query. + * @type string $groupby The GROUP BY clause of the query. + * @type string $join The JOIN clause of the query. + * @type string $orderby The ORDER BY clause of the query. + * @type string $distinct The DISTINCT clause of the query. + * @type string $fields The SELECT clause of the query. + * @type string $limits The LIMIT clause of the query. + * } */ - $split_the_query = apply_filters( 'split_the_query', $split_the_query, $this ); + $split_the_query = apply_filters( 'split_the_query', $split_the_query, $this, $old_request, compact( $pieces ) ); if ( $split_the_query ) { // First get the IDs and then fill in the objects. - $this->request = " - SELECT $found_rows $distinct {$wpdb->posts}.ID - FROM {$wpdb->posts} $join - WHERE 1=1 $where - $groupby - $orderby - $limits - "; + // Beginning of the string is on a new line to prevent leading whitespace. See https://core.trac.wordpress.org/ticket/56841. + $this->request = + "SELECT $found_rows $distinct {$wpdb->posts}.ID + FROM {$wpdb->posts} $join + WHERE 1=1 $where + $groupby + $orderby + $limits"; /** * Filters the Post IDs SQL request before sending. @@ -3126,12 +3354,12 @@ */ $this->request = apply_filters( 'posts_request_ids', $this->request, $this ); - $ids = $wpdb->get_col( $this->request ); - - if ( $ids ) { - $this->posts = $ids; + $post_ids = $wpdb->get_col( $this->request ); + + if ( $post_ids ) { + $this->posts = $post_ids; $this->set_found_posts( $q, $limits ); - _prime_post_caches( $ids, $q['update_post_term_cache'], $q['update_post_meta_cache'] ); + _prime_post_caches( $post_ids, $q['update_post_term_cache'], $q['update_post_meta_cache'] ); } else { $this->posts = array(); } @@ -3147,6 +3375,20 @@ $this->posts = array_map( 'get_post', $this->posts ); } + $unfiltered_posts = $this->posts; + + if ( $q['cache_results'] && $id_query_is_cacheable && ! $cache_found ) { + $post_ids = wp_list_pluck( $this->posts, 'ID' ); + + $cache_value = array( + 'posts' => $post_ids, + 'found_posts' => $this->found_posts, + 'max_num_pages' => $this->max_num_pages, + ); + + wp_cache_set( $cache_key, $cache_value, 'post-queries' ); + } + if ( ! $q['suppress_filters'] ) { /** * Filters the raw post results array, prior to status checks. @@ -3179,16 +3421,16 @@ $comments_request = "SELECT {$wpdb->comments}.comment_ID FROM {$wpdb->comments} $cjoin $cwhere $cgroupby $corderby $climits"; - $key = md5( $comments_request ); - $last_changed = wp_cache_get_last_changed( 'comment' ); - - $cache_key = "comment_feed:$key:$last_changed"; - $comment_ids = wp_cache_get( $cache_key, 'comment' ); + $comment_key = md5( $comments_request ); + $comment_last_changed = wp_cache_get_last_changed( 'comment' ); + + $comment_cache_key = "comment_feed:$comment_key:$comment_last_changed"; + $comment_ids = wp_cache_get( $comment_cache_key, 'comment-queries' ); if ( false === $comment_ids ) { $comment_ids = $wpdb->get_col( $comments_request ); - wp_cache_add( $cache_key, $comment_ids, 'comment' ); + wp_cache_add( $comment_cache_key, $comment_ids, 'comment-queries' ); } - _prime_comment_caches( $comment_ids, false ); + _prime_comment_caches( $comment_ids ); // Convert to WP_Comment. /** @var WP_Comment[] */ @@ -3268,7 +3510,7 @@ // Move to front, after other stickies. array_splice( $this->posts, $sticky_offset, 0, array( $sticky_post ) ); // Increment the sticky offset. The next sticky will be placed at this offset. - $sticky_offset++; + ++$sticky_offset; // Remove post from sticky posts array. $offset = array_search( $sticky_post->ID, $sticky_posts, true ); unset( $sticky_posts[ $offset ] ); @@ -3298,16 +3540,11 @@ foreach ( $stickies as $sticky_post ) { array_splice( $this->posts, $sticky_offset, 0, array( $sticky_post ) ); - $sticky_offset++; + ++$sticky_offset; } } } - // If comments have been fetched as part of the query, make sure comment meta lazy-loading is set up. - if ( ! empty( $this->comments ) ) { - wp_queue_comments_for_comment_meta_lazyload( $this->comments ); - } - if ( ! $q['suppress_filters'] ) { /** * Filters the array of retrieved posts after they've been fetched and @@ -3321,8 +3558,10 @@ $this->posts = apply_filters_ref_array( 'the_posts', array( $this->posts, &$this ) ); } - // Ensure that any posts added/modified via one of the filters above are - // of the type WP_Post and are filtered. + /* + * Ensure that any posts added/modified via one of the filters above are + * of the type WP_Post and are filtered. + */ if ( $this->posts ) { $this->post_count = count( $this->posts ); @@ -3330,7 +3569,12 @@ $this->posts = array_map( 'get_post', $this->posts ); if ( $q['cache_results'] ) { - update_post_caches( $this->posts, $post_type, $q['update_post_term_cache'], $q['update_post_meta_cache'] ); + if ( $is_unfiltered_query && $unfiltered_posts === $this->posts ) { + update_post_caches( $this->posts, $post_type, $q['update_post_term_cache'], $q['update_post_meta_cache'] ); + } else { + $post_ids = wp_list_pluck( $this->posts, 'ID' ); + _prime_post_caches( $post_ids, $q['update_post_term_cache'], $q['update_post_meta_cache'] ); + } } /** @var WP_Post */ @@ -3340,6 +3584,10 @@ $this->posts = array(); } + if ( ! empty( $this->posts ) && $q['update_menu_item_cache'] ) { + update_menu_item_cache( $this->posts ); + } + if ( $q['lazy_load_term_meta'] ) { wp_queue_posts_for_term_meta_lazyload( $this->posts ); } @@ -3348,7 +3596,7 @@ } /** - * Set up the amount of found posts and the number of pages (if limit clause was used) + * Sets up the amount of found posts and the number of pages (if limit clause was used) * for the current query. * * @since 3.5.0 @@ -3361,8 +3609,10 @@ private function set_found_posts( $q, $limits ) { global $wpdb; - // Bail if posts is an empty array. Continue if posts is an empty string, - // null, or false to accommodate caching plugins that fill posts later. + /* + * Bail if posts is an empty array. Continue if posts is an empty string, + * null, or false to accommodate caching plugins that fill posts later. + */ if ( $q['no_found_rows'] || ( is_array( $this->posts ) && ! $this->posts ) ) { return; } @@ -3402,12 +3652,12 @@ $this->found_posts = (int) apply_filters_ref_array( 'found_posts', array( $this->found_posts, &$this ) ); if ( ! empty( $limits ) ) { - $this->max_num_pages = ceil( $this->found_posts / $q['posts_per_page'] ); + $this->max_num_pages = (int) ceil( $this->found_posts / $q['posts_per_page'] ); } } /** - * Set up the next post and iterate current post index. + * Sets up the next post and iterate current post index. * * @since 1.5.0 * @@ -3415,7 +3665,7 @@ */ public function next_post() { - $this->current_post++; + ++$this->current_post; /** @var WP_Post */ $this->post = $this->posts[ $this->current_post ]; @@ -3434,7 +3684,21 @@ */ public function the_post() { global $post; + + if ( ! $this->in_the_loop ) { + // Only prime the post cache for queries limited to the ID field. + $post_ids = array_filter( $this->posts, 'is_numeric' ); + // Exclude any falsey values, such as 0. + $post_ids = array_filter( $post_ids ); + if ( $post_ids ) { + _prime_post_caches( $post_ids, $this->query_vars['update_post_term_cache'], $this->query_vars['update_post_meta_cache'] ); + } + $post_objects = array_map( 'get_post', $this->posts ); + update_post_author_caches( $post_objects ); + } + $this->in_the_loop = true; + $this->before_loop = false; if ( -1 == $this->current_post ) { // Loop has just started. /** @@ -3475,6 +3739,8 @@ // Do some cleaning up after the loop. $this->rewind_posts(); } elseif ( 0 === $this->post_count ) { + $this->before_loop = false; + /** * Fires if no results are found in a post query. * @@ -3490,7 +3756,7 @@ } /** - * Rewind the posts and reset post index. + * Rewinds the posts and resets post index. * * @since 1.5.0 */ @@ -3502,14 +3768,14 @@ } /** - * Iterate current comment index and return WP_Comment object. + * Iterates current comment index and returns WP_Comment object. * * @since 2.2.0 * * @return WP_Comment Comment object. */ public function next_comment() { - $this->current_comment++; + ++$this->current_comment; /** @var WP_Comment */ $this->comment = $this->comments[ $this->current_comment ]; @@ -3539,7 +3805,7 @@ } /** - * Whether there are more comments available. + * Determines whether there are more comments available. * * Automatically rewinds comments when finished. * @@ -3558,7 +3824,7 @@ } /** - * Rewind the comments, resets the comment index and comment to first. + * Rewinds the comments, resets the comment index and comment to first. * * @since 2.2.0 */ @@ -3720,7 +3986,7 @@ } /** - * Make private properties readable for backward compatibility. + * Makes private properties readable for backward compatibility. * * @since 4.0.0 * @@ -3734,7 +4000,7 @@ } /** - * Make private properties checkable for backward compatibility. + * Makes private properties checkable for backward compatibility. * * @since 4.0.0 * @@ -3748,7 +4014,7 @@ } /** - * Make private/protected methods readable for backward compatibility. + * Makes private/protected methods readable for backward compatibility. * * @since 4.0.0 * @@ -3764,7 +4030,7 @@ } /** - * Is the query for an existing archive page? + * Determines whether the query is for an existing archive page. * * Archive pages include category, tag, author, date, custom post type, * and custom taxonomy based archives. @@ -3785,7 +4051,7 @@ } /** - * Is the query for an existing post type archive page? + * Determines whether the query is for an existing post type archive page. * * @since 3.1.0 * @@ -3804,11 +4070,15 @@ } $post_type_object = get_post_type_object( $post_type ); + if ( ! $post_type_object ) { + return false; + } + return in_array( $post_type_object->name, (array) $post_types, true ); } /** - * Is the query for an existing attachment page? + * Determines whether the query is for an existing attachment page. * * @since 3.1.0 * @@ -3828,6 +4098,9 @@ $attachment = array_map( 'strval', (array) $attachment ); $post_obj = $this->get_queried_object(); + if ( ! $post_obj ) { + return false; + } if ( in_array( (string) $post_obj->ID, $attachment, true ) ) { return true; @@ -3840,7 +4113,7 @@ } /** - * Is the query for an existing author archive page? + * Determines whether the query is for an existing author archive page. * * If the $author parameter is specified, this function will additionally * check if the query is for one of the authors specified. @@ -3861,6 +4134,9 @@ } $author_obj = $this->get_queried_object(); + if ( ! $author_obj ) { + return false; + } $author = array_map( 'strval', (array) $author ); @@ -3876,7 +4152,7 @@ } /** - * Is the query for an existing category archive page? + * Determines whether the query is for an existing category archive page. * * If the $category parameter is specified, this function will additionally * check if the query is for one of the categories specified. @@ -3897,6 +4173,9 @@ } $cat_obj = $this->get_queried_object(); + if ( ! $cat_obj ) { + return false; + } $category = array_map( 'strval', (array) $category ); @@ -3912,7 +4191,7 @@ } /** - * Is the query for an existing tag archive page? + * Determines whether the query is for an existing tag archive page. * * If the $tag parameter is specified, this function will additionally * check if the query is for one of the tags specified. @@ -3933,6 +4212,9 @@ } $tag_obj = $this->get_queried_object(); + if ( ! $tag_obj ) { + return false; + } $tag = array_map( 'strval', (array) $tag ); @@ -3948,7 +4230,7 @@ } /** - * Is the query for an existing custom taxonomy archive page? + * Determines whether the query is for an existing custom taxonomy archive page. * * If the $taxonomy parameter is specified, this function will additionally * check if the query is for that specific $taxonomy. @@ -4004,7 +4286,7 @@ } /** - * Whether the current URL is within the comments popup window. + * Determines whether the current URL is within the comments popup window. * * @since 3.1.0 * @deprecated 4.5.0 @@ -4018,7 +4300,7 @@ } /** - * Is the query for an existing date archive? + * Determines whether the query is for an existing date archive. * * @since 3.1.0 * @@ -4029,7 +4311,7 @@ } /** - * Is the query for an existing day archive? + * Determines whether the query is for an existing day archive. * * @since 3.1.0 * @@ -4040,7 +4322,7 @@ } /** - * Is the query for a feed? + * Determines whether the query is for a feed. * * @since 3.1.0 * @@ -4062,7 +4344,7 @@ } /** - * Is the query for a comments feed? + * Determines whether the query is for a comments feed. * * @since 3.1.0 * @@ -4073,7 +4355,7 @@ } /** - * Is the query for the front page of the site? + * Determines whether the query is for the front page of the site. * * This is for what is displayed at your site's main URL. * @@ -4082,7 +4364,7 @@ * If you set a static page for the front page of your site, this function will return * true when viewing that page. * - * Otherwise the same as @see WP_Query::is_home() + * Otherwise the same as {@see WP_Query::is_home()}. * * @since 3.1.0 * @@ -4102,7 +4384,7 @@ } /** - * Is the query for the blog homepage? + * Determines whether the query is for the blog homepage. * * This is the page which shows the time based blog content of your site. * @@ -4122,7 +4404,7 @@ } /** - * Is the query for the Privacy Policy page? + * Determines whether the query is for the Privacy Policy page. * * This is the page which shows the Privacy Policy content of your site. * @@ -4145,7 +4427,7 @@ } /** - * Is the query for an existing month archive? + * Determines whether the query is for an existing month archive. * * @since 3.1.0 * @@ -4156,7 +4438,7 @@ } /** - * Is the query for an existing single page? + * Determines whether the query is for an existing single page. * * If the $page parameter is specified, this function will additionally * check if the query is for one of the pages specified. @@ -4180,6 +4462,9 @@ } $page_obj = $this->get_queried_object(); + if ( ! $page_obj ) { + return false; + } $page = array_map( 'strval', (array) $page ); @@ -4206,7 +4491,7 @@ } /** - * Is the query for a paged result and not for the first page? + * Determines whether the query is for a paged result and not for the first page. * * @since 3.1.0 * @@ -4217,7 +4502,7 @@ } /** - * Is the query for a post or page preview? + * Determines whether the query is for a post or page preview. * * @since 3.1.0 * @@ -4228,7 +4513,7 @@ } /** - * Is the query for the robots.txt file? + * Determines whether the query is for the robots.txt file. * * @since 3.1.0 * @@ -4239,7 +4524,7 @@ } /** - * Is the query for the favicon.ico file? + * Determines whether the query is for the favicon.ico file. * * @since 5.4.0 * @@ -4250,7 +4535,7 @@ } /** - * Is the query for a search? + * Determines whether the query is for a search. * * @since 3.1.0 * @@ -4261,7 +4546,7 @@ } /** - * Is the query for an existing single post? + * Determines whether the query is for an existing single post. * * Works for any post type excluding pages. * @@ -4287,6 +4572,9 @@ } $post_obj = $this->get_queried_object(); + if ( ! $post_obj ) { + return false; + } $post = array_map( 'strval', (array) $post ); @@ -4312,8 +4600,8 @@ } /** - * Is the query for an existing single post of any post type (post, attachment, page, - * custom post types)? + * Determines whether the query is for an existing single post of any post type + * (post, attachment, page, custom post types). * * If the $post_types parameter is specified, this function will additionally * check if the query is for one of the Posts Types specified. @@ -4334,12 +4622,15 @@ } $post_obj = $this->get_queried_object(); + if ( ! $post_obj ) { + return false; + } return in_array( $post_obj->post_type, (array) $post_types, true ); } /** - * Is the query for a specific time? + * Determines whether the query is for a specific time. * * @since 3.1.0 * @@ -4350,7 +4641,7 @@ } /** - * Is the query for a trackback endpoint call? + * Determines whether the query is for a trackback endpoint call. * * @since 3.1.0 * @@ -4361,7 +4652,7 @@ } /** - * Is the query for an existing year archive? + * Determines whether the query is for an existing year archive. * * @since 3.1.0 * @@ -4372,7 +4663,7 @@ } /** - * Is the query a 404 (returns no results)? + * Determines whether the query is a 404 (returns no results). * * @since 3.1.0 * @@ -4383,7 +4674,7 @@ } /** - * Is the query for an embedded post? + * Determines whether the query is for an embedded post. * * @since 4.4.0 * @@ -4394,11 +4685,11 @@ } /** - * Is the query the main query? + * Determines whether the query is the main query. * * @since 3.3.0 * - * @global WP_Query $wp_query WordPress Query object. + * @global WP_Query $wp_the_query WordPress Query object. * * @return bool Whether the query is the main query. */ @@ -4408,7 +4699,7 @@ } /** - * Set up global post data. + * Sets up global post data. * * @since 4.1.0 * @since 4.4.0 Added the ability to pass a post ID to `$post`. @@ -4467,7 +4758,7 @@ } /** - * Generate post data. + * Generates post data. * * @since 5.2.0 * @@ -4488,11 +4779,22 @@ $authordata = get_userdata( $post->post_author ); - $currentday = mysql2date( 'd.m.y', $post->post_date, false ); - $currentmonth = mysql2date( 'm', $post->post_date, false ); - $numpages = 1; - $multipage = 0; - $page = $this->get( 'page' ); + $currentday = false; + $currentmonth = false; + + $post_date = $post->post_date; + if ( ! empty( $post_date ) && '0000-00-00 00:00:00' !== $post_date ) { + // Avoid using mysql2date for performance reasons. + $currentmonth = substr( $post_date, 5, 2 ); + $day = substr( $post_date, 8, 2 ); + $year = substr( $post_date, 2, 2 ); + + $currentday = sprintf( '%s.%s.%s', $day, $currentmonth, $year ); + } + + $numpages = 1; + $multipage = 0; + $page = $this->get( 'page' ); if ( ! $page ) { $page = 1; } @@ -4510,7 +4812,7 @@ } $content = $post->post_content; - if ( false !== strpos( $content, '' ) ) { + if ( str_contains( $content, '' ) ) { $content = str_replace( "\n\n", '', $content ); $content = str_replace( "\n", '', $content ); $content = str_replace( "\n", '', $content ); @@ -4520,7 +4822,7 @@ $content = str_replace( '', '', $content ); // Ignore nextpage at the beginning of the content. - if ( 0 === strpos( $content, '' ) ) { + if ( str_starts_with( $content, '' ) ) { $content = substr( $content, 15 ); } @@ -4557,6 +4859,89 @@ return $elements; } + + /** + * Generates cache key. + * + * @since 6.1.0 + * + * @global wpdb $wpdb WordPress database abstraction object. + * + * @param array $args Query arguments. + * @param string $sql SQL statement. + * @return string Cache key. + */ + protected function generate_cache_key( array $args, $sql ) { + global $wpdb; + + unset( + $args['cache_results'], + $args['fields'], + $args['lazy_load_term_meta'], + $args['update_post_meta_cache'], + $args['update_post_term_cache'], + $args['update_menu_item_cache'], + $args['suppress_filters'] + ); + + if ( empty( $args['post_type'] ) ) { + if ( $this->is_attachment ) { + $args['post_type'] = 'attachment'; + } elseif ( $this->is_page ) { + $args['post_type'] = 'page'; + } else { + $args['post_type'] = 'post'; + } + } elseif ( 'any' === $args['post_type'] ) { + $args['post_type'] = array_values( get_post_types( array( 'exclude_from_search' => false ) ) ); + } + $args['post_type'] = (array) $args['post_type']; + // Sort post types to ensure same cache key generation. + sort( $args['post_type'] ); + + if ( isset( $args['post_status'] ) ) { + $args['post_status'] = (array) $args['post_status']; + // Sort post status to ensure same cache key generation. + sort( $args['post_status'] ); + } + + // Add a default orderby value of date to ensure same cache key generation. + if ( ! isset( $q['orderby'] ) ) { + $args['orderby'] = 'date'; + } + + $placeholder = $wpdb->placeholder_escape(); + array_walk_recursive( + $args, + /* + * Replace wpdb placeholders with the string used in the database + * query to avoid unreachable cache keys. This is necessary because + * the placeholder is randomly generated in each request. + * + * $value is passed by reference to allow it to be modified. + * array_walk_recursive() does not return an array. + */ + static function ( &$value ) use ( $wpdb, $placeholder ) { + if ( is_string( $value ) && str_contains( $value, $placeholder ) ) { + $value = $wpdb->remove_placeholder_escape( $value ); + } + } + ); + + ksort( $args ); + + // Replace wpdb placeholder in the SQL statement used by the cache key. + $sql = $wpdb->remove_placeholder_escape( $sql ); + $key = md5( serialize( $args ) . $sql ); + + $last_changed = wp_cache_get_last_changed( 'posts' ); + if ( ! empty( $this->tax_query->queries ) ) { + $last_changed .= wp_cache_get_last_changed( 'terms' ); + } + + return "wp_query:$key:$last_changed"; + } + /** * After looping through a nested query, this function * restores the $post global to the current post in this query. @@ -4573,7 +4958,7 @@ } /** - * Lazyload term meta for posts in the loop. + * Lazyloads term meta for posts in the loop. * * @since 4.4.0 * @deprecated 4.5.0 See wp_queue_posts_for_term_meta_lazyload(). @@ -4588,10 +4973,10 @@ } /** - * Lazyload comment meta for comments in the loop. + * Lazyloads comment meta for comments in the loop. * * @since 4.4.0 - * @deprecated 4.5.0 See wp_queue_comments_for_comment_meta_lazyload(). + * @deprecated 4.5.0 See wp_lazyload_comment_meta(). * * @param mixed $check * @param int $comment_id