diff --git a/src/wp-includes/class-wp-query.php b/src/wp-includes/class-wp-query.php index 21127dc755f1c..482560f819db3 100644 --- a/src/wp-includes/class-wp-query.php +++ b/src/wp-includes/class-wp-query.php @@ -1112,8 +1112,7 @@ public function parse_query( $query = '' ) { if ( ! empty( $qv['post_type'] ) ) { if ( is_array( $qv['post_type'] ) ) { - $qv['post_type'] = array_map( 'sanitize_key', array_unique( $qv['post_type'] ) ); - sort( $qv['post_type'] ); + $qv['post_type'] = array_map( 'sanitize_key', $qv['post_type'] ); } else { $qv['post_type'] = sanitize_key( $qv['post_type'] ); } @@ -1121,8 +1120,7 @@ public function parse_query( $query = '' ) { if ( ! empty( $qv['post_status'] ) ) { if ( is_array( $qv['post_status'] ) ) { - $qv['post_status'] = array_map( 'sanitize_key', array_unique( $qv['post_status'] ) ); - sort( $qv['post_status'] ); + $qv['post_status'] = array_map( 'sanitize_key', $qv['post_status'] ); } else { $qv['post_status'] = preg_replace( '|[^a-z0-9_,-]|', '', $qv['post_status'] ); } @@ -1236,8 +1234,7 @@ public function parse_tax_query( &$q ) { $cat_array = preg_split( '/[,\s]+/', urldecode( $q['cat'] ) ); $cat_array = array_map( 'intval', $cat_array ); - sort( $cat_array ); - $q['cat'] = implode( ',', $cat_array ); + $q['cat'] = implode( ',', $cat_array ); foreach ( $cat_array as $cat ) { if ( $cat > 0 ) { @@ -1279,8 +1276,7 @@ public function parse_tax_query( &$q ) { if ( ! empty( $q['category__in'] ) ) { $q['category__in'] = array_map( 'absint', array_unique( (array) $q['category__in'] ) ); - sort( $q['category__in'] ); - $tax_query[] = array( + $tax_query[] = array( 'taxonomy' => 'category', 'terms' => $q['category__in'], 'field' => 'term_id', @@ -1290,8 +1286,7 @@ public function parse_tax_query( &$q ) { 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( + $tax_query[] = array( 'taxonomy' => 'category', 'terms' => $q['category__not_in'], 'operator' => 'NOT IN', @@ -1301,8 +1296,7 @@ public function parse_tax_query( &$q ) { if ( ! empty( $q['category__and'] ) ) { $q['category__and'] = array_map( 'absint', array_unique( (array) $q['category__and'] ) ); - sort( $q['category__and'] ); - $tax_query[] = array( + $tax_query[] = array( 'taxonomy' => 'category', 'terms' => $q['category__and'], 'field' => 'term_id', @@ -1325,7 +1319,6 @@ public function parse_tax_query( &$q ) { 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'] ); @@ -1336,7 +1329,6 @@ public function parse_tax_query( &$q ) { } else { $q['tag'] = sanitize_term_field( 'slug', $q['tag'], 0, 'post_tag', 'db' ); $q['tag_slug__in'][] = $q['tag']; - sort( $q['tag_slug__in'] ); } } @@ -1350,8 +1342,7 @@ public function parse_tax_query( &$q ) { if ( ! empty( $q['tag__in'] ) ) { $q['tag__in'] = array_map( 'absint', array_unique( (array) $q['tag__in'] ) ); - sort( $q['tag__in'] ); - $tax_query[] = array( + $tax_query[] = array( 'taxonomy' => 'post_tag', 'terms' => $q['tag__in'], ); @@ -1359,8 +1350,7 @@ public function parse_tax_query( &$q ) { 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( + $tax_query[] = array( 'taxonomy' => 'post_tag', 'terms' => $q['tag__not_in'], 'operator' => 'NOT IN', @@ -1369,8 +1359,7 @@ public function parse_tax_query( &$q ) { if ( ! empty( $q['tag__and'] ) ) { $q['tag__and'] = array_map( 'absint', array_unique( (array) $q['tag__and'] ) ); - sort( $q['tag__and'] ); - $tax_query[] = array( + $tax_query[] = array( 'taxonomy' => 'post_tag', 'terms' => $q['tag__and'], 'operator' => 'AND', @@ -1379,8 +1368,7 @@ public function parse_tax_query( &$q ) { if ( ! empty( $q['tag_slug__in'] ) ) { $q['tag_slug__in'] = array_map( 'sanitize_title_for_query', array_unique( (array) $q['tag_slug__in'] ) ); - sort( $q['tag_slug__in'] ); - $tax_query[] = array( + $tax_query[] = array( 'taxonomy' => 'post_tag', 'terms' => $q['tag_slug__in'], 'field' => 'slug', @@ -1389,8 +1377,7 @@ public function parse_tax_query( &$q ) { if ( ! empty( $q['tag_slug__and'] ) ) { $q['tag_slug__and'] = array_map( 'sanitize_title_for_query', array_unique( (array) $q['tag_slug__and'] ) ); - sort( $q['tag_slug__and'] ); - $tax_query[] = array( + $tax_query[] = array( 'taxonomy' => 'post_tag', 'terms' => $q['tag_slug__and'], 'field' => 'slug', @@ -2002,6 +1989,9 @@ public function get_posts() { } else { $q['post_type'] = ''; } + } elseif ( is_array( $q['post_type'] ) ) { + $q['post_type'] = array_unique( $q['post_type'] ); + sort( $q['post_type'] ); } $post_type = $q['post_type']; if ( empty( $q['posts_per_page'] ) ) { @@ -2293,6 +2283,37 @@ public function get_posts() { // Taxonomies. if ( ! $this->is_singular ) { + // Standardize prior to re-parsing the tax query. + $sortable_arrays = array( + 'cat', + 'category__in', + 'category__not_in', + 'category__and', + 'tag_slug__in', + 'tag__in', + 'tag__not_in', + 'tag__and', + 'tag_slug__and', + ); + + foreach ( $sortable_arrays as $key ) { + if ( isset( $q[ $key ] ) && is_array( $q[ $key ] ) ) { + $q[ $key ] = array_unique( $q[ $key ] ); + sort( $q[ $key ] ); + } + } + + $sortable_comma_separated_integers = array( + 'cat', + ); + + foreach ( $sortable_comma_separated_integers as $key ) { + if ( isset( $q[ $key ] ) && is_string( $q[ $key ] ) ) { + $q[ $key ] = array_unique( array_map( 'intval', explode( ',', $q[ $key ] ) ) ); + sort( $q[ $key ] ); + } + } + $this->parse_tax_query( $q ); $clauses = $this->tax_query->get_sql( $wpdb->posts, 'ID' ); @@ -2640,6 +2661,10 @@ public function get_posts() { if ( $skip_post_status ) { $where .= $post_type_where; } elseif ( ! empty( $q['post_status'] ) ) { + if ( is_array( $q['post_status'] ) ) { + $q['post_status'] = array_unique( $q['post_status'] ); + sort( $q['post_status'] ); + } $where .= $post_type_where; diff --git a/tests/phpunit/tests/query/parseQuery.php b/tests/phpunit/tests/query/parseQuery.php index 94ced1ecd6e75..bbf3f1217fb2e 100644 --- a/tests/phpunit/tests/query/parseQuery.php +++ b/tests/phpunit/tests/query/parseQuery.php @@ -151,7 +151,7 @@ public function test_parse_query_cat_array_mixed() { ) ); - $this->assertSame( '-1,1', $q->query_vars['cat'] ); + $this->assertSame( '1,-1', $q->query_vars['cat'] ); } /** diff --git a/tests/phpunit/tests/query/thePost.php b/tests/phpunit/tests/query/thePost.php index 6032a01dbeb1b..84e824390b0a1 100644 --- a/tests/phpunit/tests/query/thePost.php +++ b/tests/phpunit/tests/query/thePost.php @@ -408,4 +408,120 @@ public function test_post_preview_links_autosaves() { } $this->assertSame( 'ticket 56992', get_the_content(), 'Permalink should show published content to logged out users' ); } + + /** + * Test that WP_Query::get() returns the value as passed on the `pre_get_posts` hook. + * + * @ticket 63255 + * @dataProvider data_pre_get_posts_includes_unmodified_query_vars + * + * @param string $query_var The query variable. + * @param mixed $query_var_value The value to set for the query variable. + */ + public function test_pre_get_posts_includes_unmodified_query_vars( $query_var, $query_var_value ) { + $number_action_runs = 0; + + /* + * MockAction can not be used here because `$query` is an object and therefore + * is passed by reference so will be modified by the time `MockAction::get_args()` + * is called. + */ + add_action( + 'pre_get_posts', + function ( $query ) use ( $query_var, $query_var_value, &$number_action_runs ) { + ++$number_action_runs; + $this->assertSame( $query_var_value, $query->get( $query_var ), 'The pre_get_posts filter should return an unmodified query var.' ); + } + ); + + new WP_Query( + array( + $query_var => $query_var_value, + 'ignore_sticky_posts' => true, // Ensures the sticky posts WP_Query does not run. + ) + ); + + // Ensure the action was called. + $this->assertSame( 1, $number_action_runs, 'The pre_get_posts action is expected to be called exactly once' ); + } + + /** + * Data provider for test_pre_get_posts_includes_unmodified_query_vars. + * + * @return array[] Data provider. + */ + public function data_pre_get_posts_includes_unmodified_query_vars() { + return array( + 'post type, string' => array( 'post_type', 'post' ), + 'post type, string[] DESC' => array( 'post_type', array( 'post', 'page' ) ), + 'post type, string[] ASC' => array( 'post_type', array( 'page', 'post' ) ), + 'post type, string[] duplicate' => array( 'post_type', array( 'post', 'post' ) ), + 'post status, string' => array( 'post_status', 'publish' ), + 'post status, string[] DESC' => array( 'post_status', array( 'publish', 'draft' ) ), + 'post status, string[] ASC' => array( 'post_status', array( 'draft', 'publish' ) ), + 'post status, string[] duplicate' => array( 'post_status', array( 'draft', 'draft' ) ), + + 'post_name__in, string' => array( 'post_name__in', 'elphaba' ), + 'post_name__in, string[] DESC' => array( 'post_name__in', array( 'the-wizard-of-oz', 'glinda', 'doctor-dillamond', 'elphaba' ) ), + 'post_name__in, string[] ASC' => array( 'post_name__in', array( 'elphaba', 'doctor-dillamond', 'glinda', 'the-wizard-of-oz' ) ), + 'post_name__in, string[] duplicate' => array( 'post_name__in', array( 'elphaba', 'doctor-dillamond', 'elphaba', 'doctor-dillamond' ) ), + + 'cat, comma-separated string ASC' => array( 'cat', '1,2' ), + 'cat, comma-separated string DESC' => array( 'cat', '2,1' ), + + 'category__in, int[] ASC' => array( 'category__in', array( 1, 2 ) ), + 'category__in, int[] DESC' => array( 'category__in', array( 2, 1 ) ), + + 'category__not_in, int[] ASC' => array( 'category__not_in', array( 1, 2 ) ), + 'category__not_in, int[] DESC' => array( 'category__not_in', array( 2, 1 ) ), + + 'category__and, int[] ASC' => array( 'category__in', array( 1, 2 ) ), + 'category__and, int[] DESC' => array( 'category__in', array( 2, 1 ) ), + + 'post id, int' => array( 'p', 1 ), + 'page_id, int' => array( 'page_id', 1 ), + 'attachment_id, int' => array( 'page_id', 1 ), + 'offset, string' => array( 'offset', '5' ), + 'offset, int' => array( 'offset', 5 ), + + 'post__in, string[] ASC' => array( 'post__in', array( '1', '2' ) ), + 'post__in, string[] DESC' => array( 'post__in', array( '2', '1' ) ), + 'post__in, int[] ASC' => array( 'post__in', array( 1, 2 ) ), + 'post__in, int[] DESC' => array( 'post__in', array( 2, 1 ) ), + 'post__in, int[] duplicate' => array( 'post__in', array( 1, 1 ) ), + + 'post__not_in, string[] ASC' => array( 'post__not_in', array( '1', '2' ) ), + 'post__not_in, string[] DESC' => array( 'post__not_in', array( '2', '1' ) ), + 'post__not_in, int[] ASC' => array( 'post__not_in', array( 1, 2 ) ), + 'post__not_in, int[] DESC' => array( 'post__not_in', array( 2, 1 ) ), + 'post__not_in, int[] duplicate' => array( 'post__not_in', array( 1, 1 ) ), + + 'author__in, string[] ASC' => array( 'author__in', array( '1', '2' ) ), + 'author__in, string[] DESC' => array( 'author__in', array( '2', '1' ) ), + 'author__in, int[] ASC' => array( 'author__in', array( 1, 2 ) ), + 'author__in, int[] DESC' => array( 'author__in', array( 2, 1 ) ), + 'author__in, int[] duplicate' => array( 'author__in', array( 1, 1 ) ), + + 'author__not_in, string[] ASC' => array( 'author__not_in', array( '1', '2' ) ), + 'author__not_in, string[] DESC' => array( 'author__not_in', array( '2', '1' ) ), + 'author__not_in, int[] ASC' => array( 'author__not_in', array( 1, 2 ) ), + 'author__not_in, int[] DESC' => array( 'author__not_in', array( 2, 1 ) ), + 'author__not_in, int[] duplicate' => array( 'author__not_in', array( 1, 1 ) ), + + 'tag_slug__in, string[] ASC' => array( 'tag_slug__in', array( 'bobby', 'hans', 'herman', 'victor' ) ), + 'tag_slug__in, string[] DESC' => array( 'tag_slug__in', array( 'victor', 'herman', 'hans', 'bobby' ) ), + + 'tag__in, int[] ASC' => array( 'tag__in', array( 1, 2 ) ), + 'tag__in, int[] DESC' => array( 'tag__in', array( 2, 1 ) ), + + 'tag__not_in, int[] ASC' => array( 'tag__not_in', array( 1, 2 ) ), + 'tag__not_in, int[] DESC' => array( 'tag__not_in', array( 2, 1 ) ), + + 'tag__and, int[] ASC' => array( 'tag__and', array( 1, 2 ) ), + 'tag__and, int[] DESC' => array( 'tag__and', array( 2, 1 ) ), + + 'tag_slug__and, string[] ASC' => array( 'tag_slug__and', array( 'bobby', 'hans', 'herman', 'victor' ) ), + 'tag_slug__and, string[] DESC' => array( 'tag_slug__and', array( 'victor', 'herman', 'hans', 'bobby' ) ), + ); + } }