diff --git a/wp-includes/class-wp-query.php b/wp-includes/class-wp-query.php index f95cb6149f..f088bda6da 100644 --- a/wp-includes/class-wp-query.php +++ b/wp-includes/class-wp-query.php @@ -474,6 +474,16 @@ class WP_Query { private $compat_methods = array( 'init_query_flags', 'parse_tax_query' ); + /** + * The cache key generated by the query. + * + * The cache key is generated by the method ::generate_cache_key() after the + * query has been normalized. + * + * @var string + */ + private $query_cache_key = ''; + /** * Resets query flags to false. * @@ -1101,7 +1111,8 @@ class WP_Query { if ( ! empty( $qv['post_type'] ) ) { if ( is_array( $qv['post_type'] ) ) { - $qv['post_type'] = array_map( 'sanitize_key', $qv['post_type'] ); + $qv['post_type'] = array_map( 'sanitize_key', array_unique( $qv['post_type'] ) ); + sort( $qv['post_type'] ); } else { $qv['post_type'] = sanitize_key( $qv['post_type'] ); } @@ -1109,7 +1120,8 @@ class WP_Query { if ( ! empty( $qv['post_status'] ) ) { if ( is_array( $qv['post_status'] ) ) { - $qv['post_status'] = array_map( 'sanitize_key', $qv['post_status'] ); + $qv['post_status'] = array_map( 'sanitize_key', array_unique( $qv['post_status'] ) ); + sort( $qv['post_status'] ); } else { $qv['post_status'] = preg_replace( '|[^a-z0-9_,-]|', '', $qv['post_status'] ); } @@ -1182,9 +1194,12 @@ class WP_Query { $term = $q[ $t->query_var ]; - if ( is_array( $term ) ) { - $term = implode( ',', $term ); + if ( ! is_array( $term ) ) { + $term = explode( ',', $term ); + $term = array_map( 'trim', $term ); } + sort( $term ); + $term = implode( ',', $term ); if ( str_contains( $term, '+' ) ) { $terms = preg_split( '/[+]+/', $term ); @@ -1220,7 +1235,8 @@ class WP_Query { $cat_array = preg_split( '/[,\s]+/', urldecode( $q['cat'] ) ); $cat_array = array_map( 'intval', $cat_array ); - $q['cat'] = implode( ',', $cat_array ); + sort( $cat_array ); + $q['cat'] = implode( ',', $cat_array ); foreach ( $cat_array as $cat ) { if ( $cat > 0 ) { @@ -1262,7 +1278,8 @@ class WP_Query { if ( ! empty( $q['category__in'] ) ) { $q['category__in'] = array_map( 'absint', array_unique( (array) $q['category__in'] ) ); - $tax_query[] = array( + sort( $q['category__in'] ); + $tax_query[] = array( 'taxonomy' => 'category', 'terms' => $q['category__in'], 'field' => 'term_id', @@ -1272,6 +1289,7 @@ class WP_Query { if ( ! empty( $q['category__not_in'] ) ) { $q['category__not_in'] = array_map( 'absint', array_unique( (array) $q['category__not_in'] ) ); + sort( $q['category__not_in'] ); $tax_query[] = array( 'taxonomy' => 'category', 'terms' => $q['category__not_in'], @@ -1282,7 +1300,8 @@ class WP_Query { if ( ! empty( $q['category__and'] ) ) { $q['category__and'] = array_map( 'absint', array_unique( (array) $q['category__and'] ) ); - $tax_query[] = array( + sort( $q['category__and'] ); + $tax_query[] = array( 'taxonomy' => 'category', 'terms' => $q['category__and'], 'field' => 'term_id', @@ -1300,10 +1319,12 @@ class WP_Query { if ( '' !== $q['tag'] && ! $this->is_singular && $this->query_vars_changed ) { if ( str_contains( $q['tag'], ',' ) ) { + // @todo Handle normalizing `tag` query string. $tags = preg_split( '/[,\r\n\t ]+/', $q['tag'] ); foreach ( (array) $tags as $tag ) { $tag = sanitize_term_field( 'slug', $tag, 0, 'post_tag', 'db' ); $q['tag_slug__in'][] = $tag; + sort( $q['tag_slug__in'] ); } } elseif ( preg_match( '/[+\r\n\t ]+/', $q['tag'] ) || ! empty( $q['cat'] ) ) { $tags = preg_split( '/[+\r\n\t ]+/', $q['tag'] ); @@ -1314,6 +1335,7 @@ class WP_Query { } else { $q['tag'] = sanitize_term_field( 'slug', $q['tag'], 0, 'post_tag', 'db' ); $q['tag_slug__in'][] = $q['tag']; + sort( $q['tag_slug__in'] ); } } @@ -1327,7 +1349,8 @@ class WP_Query { if ( ! empty( $q['tag__in'] ) ) { $q['tag__in'] = array_map( 'absint', array_unique( (array) $q['tag__in'] ) ); - $tax_query[] = array( + sort( $q['tag__in'] ); + $tax_query[] = array( 'taxonomy' => 'post_tag', 'terms' => $q['tag__in'], ); @@ -1335,6 +1358,7 @@ class WP_Query { if ( ! empty( $q['tag__not_in'] ) ) { $q['tag__not_in'] = array_map( 'absint', array_unique( (array) $q['tag__not_in'] ) ); + sort( $q['tag__not_in'] ); $tax_query[] = array( 'taxonomy' => 'post_tag', 'terms' => $q['tag__not_in'], @@ -1344,7 +1368,8 @@ class WP_Query { if ( ! empty( $q['tag__and'] ) ) { $q['tag__and'] = array_map( 'absint', array_unique( (array) $q['tag__and'] ) ); - $tax_query[] = array( + sort( $q['tag__and'] ); + $tax_query[] = array( 'taxonomy' => 'post_tag', 'terms' => $q['tag__and'], 'operator' => 'AND', @@ -1353,7 +1378,8 @@ class WP_Query { if ( ! empty( $q['tag_slug__in'] ) ) { $q['tag_slug__in'] = array_map( 'sanitize_title_for_query', array_unique( (array) $q['tag_slug__in'] ) ); - $tax_query[] = array( + sort( $q['tag_slug__in'] ); + $tax_query[] = array( 'taxonomy' => 'post_tag', 'terms' => $q['tag_slug__in'], 'field' => 'slug', @@ -1362,7 +1388,8 @@ class WP_Query { if ( ! empty( $q['tag_slug__and'] ) ) { $q['tag_slug__and'] = array_map( 'sanitize_title_for_query', array_unique( (array) $q['tag_slug__and'] ) ); - $tax_query[] = array( + sort( $q['tag_slug__and'] ); + $tax_query[] = array( 'taxonomy' => 'post_tag', 'terms' => $q['tag_slug__and'], 'field' => 'slug', @@ -2186,8 +2213,11 @@ class WP_Query { $where .= " AND {$wpdb->posts}.post_name = '" . $q['attachment'] . "'"; } elseif ( is_array( $q['post_name__in'] ) && ! empty( $q['post_name__in'] ) ) { $q['post_name__in'] = array_map( 'sanitize_title_for_query', $q['post_name__in'] ); - $post_name__in = "'" . implode( "','", $q['post_name__in'] ) . "'"; - $where .= " AND {$wpdb->posts}.post_name IN ($post_name__in)"; + // Duplicate array before sorting to allow for the orderby clause. + $post_name__in_for_where = array_unique( $q['post_name__in'] ); + sort( $post_name__in_for_where ); + $post_name__in = "'" . implode( "','", $post_name__in_for_where ) . "'"; + $where .= " AND {$wpdb->posts}.post_name IN ($post_name__in)"; } // If an attachment is requested by number, let it supersede any post number. @@ -2199,9 +2229,14 @@ class WP_Query { if ( $q['p'] ) { $where .= " AND {$wpdb->posts}.ID = " . $q['p']; } elseif ( $q['post__in'] ) { - $post__in = implode( ',', array_map( 'absint', $q['post__in'] ) ); + // Duplicate array before sorting to allow for the orderby clause. + $post__in_for_where = $q['post__in']; + $post__in_for_where = array_unique( array_map( 'absint', $post__in_for_where ) ); + sort( $post__in_for_where ); + $post__in = implode( ',', array_map( 'absint', $post__in_for_where ) ); $where .= " AND {$wpdb->posts}.ID IN ($post__in)"; } elseif ( $q['post__not_in'] ) { + sort( $q['post__not_in'] ); $post__not_in = implode( ',', array_map( 'absint', $q['post__not_in'] ) ); $where .= " AND {$wpdb->posts}.ID NOT IN ($post__not_in)"; } @@ -2209,9 +2244,14 @@ class WP_Query { if ( is_numeric( $q['post_parent'] ) ) { $where .= $wpdb->prepare( " AND {$wpdb->posts}.post_parent = %d ", $q['post_parent'] ); } elseif ( $q['post_parent__in'] ) { - $post_parent__in = implode( ',', array_map( 'absint', $q['post_parent__in'] ) ); + // Duplicate array before sorting to allow for the orderby clause. + $post_parent__in_for_where = $q['post_parent__in']; + $post_parent__in_for_where = array_unique( array_map( 'absint', $post_parent__in_for_where ) ); + sort( $post_parent__in_for_where ); + $post_parent__in = implode( ',', array_map( 'absint', $post_parent__in_for_where ) ); $where .= " AND {$wpdb->posts}.post_parent IN ($post_parent__in)"; } elseif ( $q['post_parent__not_in'] ) { + sort( $q['post_parent__not_in'] ); $post_parent__not_in = implode( ',', array_map( 'absint', $q['post_parent__not_in'] ) ); $where .= " AND {$wpdb->posts}.post_parent NOT IN ($post_parent__not_in)"; } @@ -2341,6 +2381,7 @@ class WP_Query { if ( ! empty( $q['author'] ) && '0' != $q['author'] ) { $q['author'] = addslashes_gpc( '' . urldecode( $q['author'] ) ); $authors = array_unique( array_map( 'intval', preg_split( '/[,\s]+/', $q['author'] ) ) ); + sort( $authors ); foreach ( $authors as $author ) { $key = $author > 0 ? 'author__in' : 'author__not_in'; $q[ $key ][] = abs( $author ); @@ -2349,9 +2390,17 @@ class WP_Query { } if ( ! empty( $q['author__not_in'] ) ) { - $author__not_in = implode( ',', array_map( 'absint', array_unique( (array) $q['author__not_in'] ) ) ); + if ( is_array( $q['author__not_in'] ) ) { + $q['author__not_in'] = array_unique( array_map( 'absint', $q['author__not_in'] ) ); + sort( $q['author__not_in'] ); + } + $author__not_in = implode( ',', (array) $q['author__not_in'] ); $where .= " AND {$wpdb->posts}.post_author NOT IN ($author__not_in) "; } elseif ( ! empty( $q['author__in'] ) ) { + if ( is_array( $q['author__in'] ) ) { + $q['author__in'] = array_unique( array_map( 'absint', $q['author__in'] ) ); + sort( $q['author__in'] ); + } $author__in = implode( ',', array_map( 'absint', array_unique( (array) $q['author__in'] ) ) ); $where .= " AND {$wpdb->posts}.post_author IN ($author__in) "; } @@ -2588,6 +2637,7 @@ class WP_Query { if ( ! is_array( $q_status ) ) { $q_status = explode( ',', $q_status ); } + sort( $q_status ); $r_status = array(); $p_status = array(); $e_status = array(); @@ -4902,6 +4952,33 @@ class WP_Query { // Sort post types to ensure same cache key generation. sort( $args['post_type'] ); + /* + * Sort arrays that can be used for ordering prior to cache key generation. + * + * These arrays are sorted in the query generator for the purposes of the + * WHERE clause but the arguments are not modified as they can be used for + * the orderby clase. + * + * Their use in the orderby clause will generate a different SQL query so + * they can be sorted for the cache key generation. + */ + $sortable_arrays_with_int_values = array( + 'post__in', + 'post_parent__in', + ); + foreach ( $sortable_arrays_with_int_values as $key ) { + if ( isset( $args[ $key ] ) && is_array( $args[ $key ] ) ) { + $args[ $key ] = array_unique( array_map( 'absint', $args[ $key ] ) ); + sort( $args[ $key ] ); + } + } + + // Sort and unique the 'post_name__in' for cache key generation. + if ( isset( $args['post_name__in'] ) && is_array( $args['post_name__in'] ) ) { + $args['post_name__in'] = array_unique( $args['post_name__in'] ); + sort( $args['post_name__in'] ); + } + if ( isset( $args['post_status'] ) ) { $args['post_status'] = (array) $args['post_status']; // Sort post status to ensure same cache key generation. @@ -4942,7 +5019,8 @@ class WP_Query { $last_changed .= wp_cache_get_last_changed( 'terms' ); } - return "wp_query:$key:$last_changed"; + $this->query_cache_key = "wp_query:$key:$last_changed"; + return $this->query_cache_key; } /** diff --git a/wp-includes/version.php b/wp-includes/version.php index 2b4a31adfb..6f132c009a 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -16,7 +16,7 @@ * * @global string $wp_version */ -$wp_version = '6.8-alpha-59765'; +$wp_version = '6.8-alpha-59766'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.