@@ -474,6 +474,16 @@ class WP_Query {
474474
475475 private $ compat_methods = array ( 'init_query_flags ' , 'parse_tax_query ' );
476476
477+ /**
478+ * The cache key generated by the query.
479+ *
480+ * The cache key is generated by the method ::generate_cache_key() after the
481+ * query has been normalized.
482+ *
483+ * @var string
484+ */
485+ private $ query_cache_key = '' ;
486+
477487 /**
478488 * Resets query flags to false.
479489 *
@@ -1101,15 +1111,17 @@ public function parse_query( $query = '' ) {
11011111
11021112 if ( ! empty ( $ qv ['post_type ' ] ) ) {
11031113 if ( is_array ( $ qv ['post_type ' ] ) ) {
1104- $ qv ['post_type ' ] = array_map ( 'sanitize_key ' , $ qv ['post_type ' ] );
1114+ $ qv ['post_type ' ] = array_map ( 'sanitize_key ' , array_unique ( $ qv ['post_type ' ] ) );
1115+ sort ( $ qv ['post_type ' ] );
11051116 } else {
11061117 $ qv ['post_type ' ] = sanitize_key ( $ qv ['post_type ' ] );
11071118 }
11081119 }
11091120
11101121 if ( ! empty ( $ qv ['post_status ' ] ) ) {
11111122 if ( is_array ( $ qv ['post_status ' ] ) ) {
1112- $ qv ['post_status ' ] = array_map ( 'sanitize_key ' , $ qv ['post_status ' ] );
1123+ $ qv ['post_status ' ] = array_map ( 'sanitize_key ' , array_unique ( $ qv ['post_status ' ] ) );
1124+ sort ( $ qv ['post_status ' ] );
11131125 } else {
11141126 $ qv ['post_status ' ] = preg_replace ( '|[^a-z0-9_,-]| ' , '' , $ qv ['post_status ' ] );
11151127 }
@@ -1182,9 +1194,12 @@ public function parse_tax_query( &$q ) {
11821194
11831195 $ term = $ q [ $ t ->query_var ];
11841196
1185- if ( is_array ( $ term ) ) {
1186- $ term = implode ( ', ' , $ term );
1197+ if ( ! is_array ( $ term ) ) {
1198+ $ term = explode ( ', ' , $ term );
1199+ $ term = array_map ( 'trim ' , $ term );
11871200 }
1201+ sort ( $ term );
1202+ $ term = implode ( ', ' , $ term );
11881203
11891204 if ( str_contains ( $ term , '+ ' ) ) {
11901205 $ terms = preg_split ( '/[+]+/ ' , $ term );
@@ -1220,7 +1235,8 @@ public function parse_tax_query( &$q ) {
12201235
12211236 $ cat_array = preg_split ( '/[,\s]+/ ' , urldecode ( $ q ['cat ' ] ) );
12221237 $ cat_array = array_map ( 'intval ' , $ cat_array );
1223- $ q ['cat ' ] = implode ( ', ' , $ cat_array );
1238+ sort ( $ cat_array );
1239+ $ q ['cat ' ] = implode ( ', ' , $ cat_array );
12241240
12251241 foreach ( $ cat_array as $ cat ) {
12261242 if ( $ cat > 0 ) {
@@ -1262,7 +1278,8 @@ public function parse_tax_query( &$q ) {
12621278
12631279 if ( ! empty ( $ q ['category__in ' ] ) ) {
12641280 $ q ['category__in ' ] = array_map ( 'absint ' , array_unique ( (array ) $ q ['category__in ' ] ) );
1265- $ tax_query [] = array (
1281+ sort ( $ q ['category__in ' ] );
1282+ $ tax_query [] = array (
12661283 'taxonomy ' => 'category ' ,
12671284 'terms ' => $ q ['category__in ' ],
12681285 'field ' => 'term_id ' ,
@@ -1272,6 +1289,7 @@ public function parse_tax_query( &$q ) {
12721289
12731290 if ( ! empty ( $ q ['category__not_in ' ] ) ) {
12741291 $ q ['category__not_in ' ] = array_map ( 'absint ' , array_unique ( (array ) $ q ['category__not_in ' ] ) );
1292+ sort ( $ q ['category__not_in ' ] );
12751293 $ tax_query [] = array (
12761294 'taxonomy ' => 'category ' ,
12771295 'terms ' => $ q ['category__not_in ' ],
@@ -1282,7 +1300,8 @@ public function parse_tax_query( &$q ) {
12821300
12831301 if ( ! empty ( $ q ['category__and ' ] ) ) {
12841302 $ q ['category__and ' ] = array_map ( 'absint ' , array_unique ( (array ) $ q ['category__and ' ] ) );
1285- $ tax_query [] = array (
1303+ sort ( $ q ['category__and ' ] );
1304+ $ tax_query [] = array (
12861305 'taxonomy ' => 'category ' ,
12871306 'terms ' => $ q ['category__and ' ],
12881307 'field ' => 'term_id ' ,
@@ -1300,10 +1319,12 @@ public function parse_tax_query( &$q ) {
13001319
13011320 if ( '' !== $ q ['tag ' ] && ! $ this ->is_singular && $ this ->query_vars_changed ) {
13021321 if ( str_contains ( $ q ['tag ' ], ', ' ) ) {
1322+ // @todo Handle normalizing `tag` query string.
13031323 $ tags = preg_split ( '/[,\r\n\t ]+/ ' , $ q ['tag ' ] );
13041324 foreach ( (array ) $ tags as $ tag ) {
13051325 $ tag = sanitize_term_field ( 'slug ' , $ tag , 0 , 'post_tag ' , 'db ' );
13061326 $ q ['tag_slug__in ' ][] = $ tag ;
1327+ sort ( $ q ['tag_slug__in ' ] );
13071328 }
13081329 } elseif ( preg_match ( '/[+\r\n\t ]+/ ' , $ q ['tag ' ] ) || ! empty ( $ q ['cat ' ] ) ) {
13091330 $ tags = preg_split ( '/[+\r\n\t ]+/ ' , $ q ['tag ' ] );
@@ -1314,6 +1335,7 @@ public function parse_tax_query( &$q ) {
13141335 } else {
13151336 $ q ['tag ' ] = sanitize_term_field ( 'slug ' , $ q ['tag ' ], 0 , 'post_tag ' , 'db ' );
13161337 $ q ['tag_slug__in ' ][] = $ q ['tag ' ];
1338+ sort ( $ q ['tag_slug__in ' ] );
13171339 }
13181340 }
13191341
@@ -1327,14 +1349,16 @@ public function parse_tax_query( &$q ) {
13271349
13281350 if ( ! empty ( $ q ['tag__in ' ] ) ) {
13291351 $ q ['tag__in ' ] = array_map ( 'absint ' , array_unique ( (array ) $ q ['tag__in ' ] ) );
1330- $ tax_query [] = array (
1352+ sort ( $ q ['tag__in ' ] );
1353+ $ tax_query [] = array (
13311354 'taxonomy ' => 'post_tag ' ,
13321355 'terms ' => $ q ['tag__in ' ],
13331356 );
13341357 }
13351358
13361359 if ( ! empty ( $ q ['tag__not_in ' ] ) ) {
13371360 $ q ['tag__not_in ' ] = array_map ( 'absint ' , array_unique ( (array ) $ q ['tag__not_in ' ] ) );
1361+ sort ( $ q ['tag__not_in ' ] );
13381362 $ tax_query [] = array (
13391363 'taxonomy ' => 'post_tag ' ,
13401364 'terms ' => $ q ['tag__not_in ' ],
@@ -1344,7 +1368,8 @@ public function parse_tax_query( &$q ) {
13441368
13451369 if ( ! empty ( $ q ['tag__and ' ] ) ) {
13461370 $ q ['tag__and ' ] = array_map ( 'absint ' , array_unique ( (array ) $ q ['tag__and ' ] ) );
1347- $ tax_query [] = array (
1371+ sort ( $ q ['tag__and ' ] );
1372+ $ tax_query [] = array (
13481373 'taxonomy ' => 'post_tag ' ,
13491374 'terms ' => $ q ['tag__and ' ],
13501375 'operator ' => 'AND ' ,
@@ -1353,7 +1378,8 @@ public function parse_tax_query( &$q ) {
13531378
13541379 if ( ! empty ( $ q ['tag_slug__in ' ] ) ) {
13551380 $ q ['tag_slug__in ' ] = array_map ( 'sanitize_title_for_query ' , array_unique ( (array ) $ q ['tag_slug__in ' ] ) );
1356- $ tax_query [] = array (
1381+ sort ( $ q ['tag_slug__in ' ] );
1382+ $ tax_query [] = array (
13571383 'taxonomy ' => 'post_tag ' ,
13581384 'terms ' => $ q ['tag_slug__in ' ],
13591385 'field ' => 'slug ' ,
@@ -1362,7 +1388,8 @@ public function parse_tax_query( &$q ) {
13621388
13631389 if ( ! empty ( $ q ['tag_slug__and ' ] ) ) {
13641390 $ q ['tag_slug__and ' ] = array_map ( 'sanitize_title_for_query ' , array_unique ( (array ) $ q ['tag_slug__and ' ] ) );
1365- $ tax_query [] = array (
1391+ sort ( $ q ['tag_slug__and ' ] );
1392+ $ tax_query [] = array (
13661393 'taxonomy ' => 'post_tag ' ,
13671394 'terms ' => $ q ['tag_slug__and ' ],
13681395 'field ' => 'slug ' ,
@@ -2186,8 +2213,11 @@ public function get_posts() {
21862213 $ where .= " AND {$ wpdb ->posts }.post_name = ' " . $ q ['attachment ' ] . "' " ;
21872214 } elseif ( is_array ( $ q ['post_name__in ' ] ) && ! empty ( $ q ['post_name__in ' ] ) ) {
21882215 $ q ['post_name__in ' ] = array_map ( 'sanitize_title_for_query ' , $ q ['post_name__in ' ] );
2189- $ post_name__in = "' " . implode ( "',' " , $ q ['post_name__in ' ] ) . "' " ;
2190- $ where .= " AND {$ wpdb ->posts }.post_name IN ( $ post_name__in) " ;
2216+ // Duplicate array before sorting to allow for the orderby clause.
2217+ $ post_name__in_for_where = array_unique ( $ q ['post_name__in ' ] );
2218+ sort ( $ post_name__in_for_where );
2219+ $ post_name__in = "' " . implode ( "',' " , $ post_name__in_for_where ) . "' " ;
2220+ $ where .= " AND {$ wpdb ->posts }.post_name IN ( $ post_name__in) " ;
21912221 }
21922222
21932223 // If an attachment is requested by number, let it supersede any post number.
@@ -2199,19 +2229,29 @@ public function get_posts() {
21992229 if ( $ q ['p ' ] ) {
22002230 $ where .= " AND {$ wpdb ->posts }.ID = " . $ q ['p ' ];
22012231 } elseif ( $ q ['post__in ' ] ) {
2202- $ post__in = implode ( ', ' , array_map ( 'absint ' , $ q ['post__in ' ] ) );
2232+ // Duplicate array before sorting to allow for the orderby clause.
2233+ $ post__in_for_where = $ q ['post__in ' ];
2234+ $ post__in_for_where = array_unique ( array_map ( 'absint ' , $ post__in_for_where ) );
2235+ sort ( $ post__in_for_where );
2236+ $ post__in = implode ( ', ' , array_map ( 'absint ' , $ post__in_for_where ) );
22032237 $ where .= " AND {$ wpdb ->posts }.ID IN ( $ post__in) " ;
22042238 } elseif ( $ q ['post__not_in ' ] ) {
2239+ sort ( $ q ['post__not_in ' ] );
22052240 $ post__not_in = implode ( ', ' , array_map ( 'absint ' , $ q ['post__not_in ' ] ) );
22062241 $ where .= " AND {$ wpdb ->posts }.ID NOT IN ( $ post__not_in) " ;
22072242 }
22082243
22092244 if ( is_numeric ( $ q ['post_parent ' ] ) ) {
22102245 $ where .= $ wpdb ->prepare ( " AND {$ wpdb ->posts }.post_parent = %d " , $ q ['post_parent ' ] );
22112246 } elseif ( $ q ['post_parent__in ' ] ) {
2212- $ post_parent__in = implode ( ', ' , array_map ( 'absint ' , $ q ['post_parent__in ' ] ) );
2247+ // Duplicate array before sorting to allow for the orderby clause.
2248+ $ post_parent__in_for_where = $ q ['post_parent__in ' ];
2249+ $ post_parent__in_for_where = array_unique ( array_map ( 'absint ' , $ post_parent__in_for_where ) );
2250+ sort ( $ post_parent__in_for_where );
2251+ $ post_parent__in = implode ( ', ' , array_map ( 'absint ' , $ post_parent__in_for_where ) );
22132252 $ where .= " AND {$ wpdb ->posts }.post_parent IN ( $ post_parent__in) " ;
22142253 } elseif ( $ q ['post_parent__not_in ' ] ) {
2254+ sort ( $ q ['post_parent__not_in ' ] );
22152255 $ post_parent__not_in = implode ( ', ' , array_map ( 'absint ' , $ q ['post_parent__not_in ' ] ) );
22162256 $ where .= " AND {$ wpdb ->posts }.post_parent NOT IN ( $ post_parent__not_in) " ;
22172257 }
@@ -2341,6 +2381,7 @@ public function get_posts() {
23412381 if ( ! empty ( $ q ['author ' ] ) && '0 ' != $ q ['author ' ] ) {
23422382 $ q ['author ' ] = addslashes_gpc ( '' . urldecode ( $ q ['author ' ] ) );
23432383 $ authors = array_unique ( array_map ( 'intval ' , preg_split ( '/[,\s]+/ ' , $ q ['author ' ] ) ) );
2384+ sort ( $ authors );
23442385 foreach ( $ authors as $ author ) {
23452386 $ key = $ author > 0 ? 'author__in ' : 'author__not_in ' ;
23462387 $ q [ $ key ][] = abs ( $ author );
@@ -2349,9 +2390,17 @@ public function get_posts() {
23492390 }
23502391
23512392 if ( ! empty ( $ q ['author__not_in ' ] ) ) {
2352- $ author__not_in = implode ( ', ' , array_map ( 'absint ' , array_unique ( (array ) $ q ['author__not_in ' ] ) ) );
2393+ if ( is_array ( $ q ['author__not_in ' ] ) ) {
2394+ $ q ['author__not_in ' ] = array_unique ( array_map ( 'absint ' , $ q ['author__not_in ' ] ) );
2395+ sort ( $ q ['author__not_in ' ] );
2396+ }
2397+ $ author__not_in = implode ( ', ' , (array ) $ q ['author__not_in ' ] );
23532398 $ where .= " AND {$ wpdb ->posts }.post_author NOT IN ( $ author__not_in) " ;
23542399 } elseif ( ! empty ( $ q ['author__in ' ] ) ) {
2400+ if ( is_array ( $ q ['author__in ' ] ) ) {
2401+ $ q ['author__in ' ] = array_unique ( array_map ( 'absint ' , $ q ['author__in ' ] ) );
2402+ sort ( $ q ['author__in ' ] );
2403+ }
23552404 $ author__in = implode ( ', ' , array_map ( 'absint ' , array_unique ( (array ) $ q ['author__in ' ] ) ) );
23562405 $ where .= " AND {$ wpdb ->posts }.post_author IN ( $ author__in) " ;
23572406 }
@@ -2588,6 +2637,7 @@ public function get_posts() {
25882637 if ( ! is_array ( $ q_status ) ) {
25892638 $ q_status = explode ( ', ' , $ q_status );
25902639 }
2640+ sort ( $ q_status );
25912641 $ r_status = array ();
25922642 $ p_status = array ();
25932643 $ e_status = array ();
@@ -4902,6 +4952,33 @@ protected function generate_cache_key( array $args, $sql ) {
49024952 // Sort post types to ensure same cache key generation.
49034953 sort ( $ args ['post_type ' ] );
49044954
4955+ /*
4956+ * Sort arrays that can be used for ordering prior to cache key generation.
4957+ *
4958+ * These arrays are sorted in the query generator for the purposes of the
4959+ * WHERE clause but the arguments are not modified as they can be used for
4960+ * the orderby clase.
4961+ *
4962+ * Their use in the orderby clause will generate a different SQL query so
4963+ * they can be sorted for the cache key generation.
4964+ */
4965+ $ sortable_arrays_with_int_values = array (
4966+ 'post__in ' ,
4967+ 'post_parent__in ' ,
4968+ );
4969+ foreach ( $ sortable_arrays_with_int_values as $ key ) {
4970+ if ( isset ( $ args [ $ key ] ) && is_array ( $ args [ $ key ] ) ) {
4971+ $ args [ $ key ] = array_unique ( array_map ( 'absint ' , $ args [ $ key ] ) );
4972+ sort ( $ args [ $ key ] );
4973+ }
4974+ }
4975+
4976+ // Sort and unique the 'post_name__in' for cache key generation.
4977+ if ( isset ( $ args ['post_name__in ' ] ) && is_array ( $ args ['post_name__in ' ] ) ) {
4978+ $ args ['post_name__in ' ] = array_unique ( $ args ['post_name__in ' ] );
4979+ sort ( $ args ['post_name__in ' ] );
4980+ }
4981+
49054982 if ( isset ( $ args ['post_status ' ] ) ) {
49064983 $ args ['post_status ' ] = (array ) $ args ['post_status ' ];
49074984 // Sort post status to ensure same cache key generation.
@@ -4942,7 +5019,8 @@ static function ( &$value ) use ( $wpdb, $placeholder ) {
49425019 $ last_changed .= wp_cache_get_last_changed ( 'terms ' );
49435020 }
49445021
4945- return "wp_query: $ key: $ last_changed " ;
5022+ $ this ->query_cache_key = "wp_query: $ key: $ last_changed " ;
5023+ return $ this ->query_cache_key ;
49465024 }
49475025
49485026 /**
0 commit comments