From 2367b302d1a34af2c91a89fa064e1cc44d7b76c5 Mon Sep 17 00:00:00 2001 From: sarequl Date: Sun, 1 Nov 2026 14:33:31 +0000 Subject: [PATCH 1/8] Administration: Guard list table request parameters against arrays. Several admin screens passed request values straight into string functions, so an array parameter caused a TypeError or an "Array to string conversion" warning. Check that each value is a string (or a scalar for the comment search query) before using it. --- src/wp-admin/edit-comments.php | 2 +- src/wp-admin/edit-tags.php | 2 +- src/wp-admin/edit.php | 10 ++++++---- .../includes/class-wp-comments-list-table.php | 2 +- src/wp-admin/includes/class-wp-list-table.php | 2 +- .../includes/class-wp-media-list-table.php | 10 ++++++++-- .../class-wp-plugin-install-list-table.php | 2 +- .../includes/class-wp-plugins-list-table.php | 4 ++-- .../includes/class-wp-terms-list-table.php | 6 +++--- .../includes/class-wp-users-list-table.php | 8 ++++---- src/wp-admin/includes/plugin-install.php | 2 +- src/wp-admin/plugins.php | 2 +- src/wp-admin/site-health.php | 6 +++--- src/wp-admin/upload.php | 2 +- src/wp-admin/users.php | 2 +- src/wp-includes/class-wp-comment-query.php | 6 +++++- tests/phpunit/tests/comment/query.php | 15 +++++++++++++++ 17 files changed, 55 insertions(+), 28 deletions(-) diff --git a/src/wp-admin/edit-comments.php b/src/wp-admin/edit-comments.php index 46cf66d393985..2229f78182021 100644 --- a/src/wp-admin/edit-comments.php +++ b/src/wp-admin/edit-comments.php @@ -275,7 +275,7 @@ } } -if ( isset( $_REQUEST['s'] ) && strlen( $_REQUEST['s'] ) ) { +if ( isset( $_REQUEST['s'] ) && is_string( $_REQUEST['s'] ) && strlen( $_REQUEST['s'] ) ) { echo ''; printf( /* translators: %s: Search query. */ diff --git a/src/wp-admin/edit-tags.php b/src/wp-admin/edit-tags.php index 6efdeb9b0527d..1edf7d351c77e 100644 --- a/src/wp-admin/edit-tags.php +++ b/src/wp-admin/edit-tags.php @@ -336,7 +336,7 @@

'; printf( /* translators: %s: Search query. */ diff --git a/src/wp-admin/edit.php b/src/wp-admin/edit.php index 15cb17d303bd3..38619d492b13f 100644 --- a/src/wp-admin/edit.php +++ b/src/wp-admin/edit.php @@ -89,7 +89,9 @@ if ( 'delete_all' === $doaction ) { // Prepare for deletion of all posts with a specified post status (i.e. Empty Trash). - $post_status = preg_replace( '/[^a-z0-9_-]+/i', '', $_REQUEST['post_status'] ); + $post_status = isset( $_REQUEST['post_status'] ) && is_string( $_REQUEST['post_status'] ) + ? preg_replace( '/[^a-z0-9_-]+/i', '', $_REQUEST['post_status'] ) + : ''; // Validate the post status exists. if ( get_post_status_object( $post_status ) ) { /** @@ -422,7 +424,7 @@ echo ' ' . esc_html( $post_type_object->labels->add_new_item ) . ''; } -if ( isset( $_REQUEST['s'] ) && strlen( $_REQUEST['s'] ) ) { +if ( isset( $_REQUEST['s'] ) && is_string( $_REQUEST['s'] ) && strlen( $_REQUEST['s'] ) ) { echo ''; printf( /* translators: %s: Search query. */ @@ -489,10 +491,10 @@ search_box( $post_type_object->labels->search_items, 'post' ); ?> - + - + diff --git a/src/wp-admin/includes/class-wp-comments-list-table.php b/src/wp-admin/includes/class-wp-comments-list-table.php index 1dff5d26755ce..294f9d4f9c3ac 100644 --- a/src/wp-admin/includes/class-wp-comments-list-table.php +++ b/src/wp-admin/includes/class-wp-comments-list-table.php @@ -107,7 +107,7 @@ public function prepare_items() { $comment_type = ''; - if ( ! empty( $_REQUEST['comment_type'] ) && ! in_array( $_REQUEST['comment_type'], array( 'note', 'reaction' ), true ) ) { + if ( ! empty( $_REQUEST['comment_type'] ) && is_string( $_REQUEST['comment_type'] ) && ! in_array( $_REQUEST['comment_type'], array( 'note', 'reaction' ), true ) ) { $comment_type = $_REQUEST['comment_type']; } diff --git a/src/wp-admin/includes/class-wp-list-table.php b/src/wp-admin/includes/class-wp-list-table.php index 08b31d5eb7ba0..3ebc26dcd8e6c 100644 --- a/src/wp-admin/includes/class-wp-list-table.php +++ b/src/wp-admin/includes/class-wp-list-table.php @@ -421,7 +421,7 @@ public function search_box( $text, $input_id ) { echo ''; } } - if ( ! empty( $_REQUEST['order'] ) ) { + if ( ! empty( $_REQUEST['order'] ) && is_string( $_REQUEST['order'] ) ) { echo ''; } if ( ! empty( $_REQUEST['post_mime_type'] ) ) { diff --git a/src/wp-admin/includes/class-wp-media-list-table.php b/src/wp-admin/includes/class-wp-media-list-table.php index feac4753d3e92..7e8eeee48ec59 100644 --- a/src/wp-admin/includes/class-wp-media-list-table.php +++ b/src/wp-admin/includes/class-wp-media-list-table.php @@ -88,7 +88,10 @@ public function ajax_user_can() { public function prepare_items() { global $mode, $wp_query, $post_mime_types, $avail_post_mime_types; - $mode = empty( $_REQUEST['mode'] ) ? 'list' : $_REQUEST['mode']; + $mode = 'list'; + if ( ! empty( $_REQUEST['mode'] ) && is_string( $_REQUEST['mode'] ) ) { + $mode = $_REQUEST['mode']; + } /* * Exclude attachments scheduled for deletion in the next two hours @@ -150,7 +153,10 @@ protected function get_views() { $type_links = array(); - $filter = empty( $_GET['attachment-filter'] ) ? '' : $_GET['attachment-filter']; + $filter = ''; + if ( ! empty( $_GET['attachment-filter'] ) && is_string( $_GET['attachment-filter'] ) ) { + $filter = $_GET['attachment-filter']; + } $type_links['all'] = sprintf( '', diff --git a/src/wp-admin/includes/class-wp-plugin-install-list-table.php b/src/wp-admin/includes/class-wp-plugin-install-list-table.php index 3f6d2a104d099..0a5fc600d9ed3 100644 --- a/src/wp-admin/includes/class-wp-plugin-install-list-table.php +++ b/src/wp-admin/includes/class-wp-plugin-install-list-table.php @@ -204,7 +204,7 @@ public function prepare_items() { switch ( $tab ) { case 'search': $type = isset( $_REQUEST['type'] ) ? wp_unslash( $_REQUEST['type'] ) : 'term'; - $term = isset( $_REQUEST['s'] ) ? wp_unslash( $_REQUEST['s'] ) : ''; + $term = isset( $_REQUEST['s'] ) && is_string( $_REQUEST['s'] ) ? wp_unslash( $_REQUEST['s'] ) : ''; switch ( $type ) { case 'tag': diff --git a/src/wp-admin/includes/class-wp-plugins-list-table.php b/src/wp-admin/includes/class-wp-plugins-list-table.php index 7778f21b4688b..53220806f8ce1 100644 --- a/src/wp-admin/includes/class-wp-plugins-list-table.php +++ b/src/wp-admin/includes/class-wp-plugins-list-table.php @@ -62,7 +62,7 @@ public function __construct( $args = array() ) { $status = sanitize_key( $_REQUEST['plugin_status'] ); } - if ( isset( $_REQUEST['s'] ) ) { + if ( isset( $_REQUEST['s'] ) && is_string( $_REQUEST['s'] ) ) { $_SERVER['REQUEST_URI'] = add_query_arg( 's', wp_unslash( $_REQUEST['s'] ) ); } @@ -449,7 +449,7 @@ public function _order_callback( $plugin_a, $plugin_b ) { public function no_items() { global $plugins; - if ( ! empty( $_REQUEST['s'] ) ) { + if ( ! empty( $_REQUEST['s'] ) && is_string( $_REQUEST['s'] ) ) { $s = esc_html( urldecode( wp_unslash( $_REQUEST['s'] ) ) ); /* translators: %s: Plugin search term. */ diff --git a/src/wp-admin/includes/class-wp-terms-list-table.php b/src/wp-admin/includes/class-wp-terms-list-table.php index 9481f0967a744..be70ed1c9b4de 100644 --- a/src/wp-admin/includes/class-wp-terms-list-table.php +++ b/src/wp-admin/includes/class-wp-terms-list-table.php @@ -109,7 +109,7 @@ public function prepare_items() { $tags_per_page = apply_filters( 'edit_categories_per_page', $tags_per_page ); } - $search = ! empty( $_REQUEST['s'] ) ? trim( wp_unslash( $_REQUEST['s'] ) ) : ''; + $search = ! empty( $_REQUEST['s'] ) && is_string( $_REQUEST['s'] ) ? trim( wp_unslash( $_REQUEST['s'] ) ) : ''; $args = array( 'taxonomy' => $taxonomy, @@ -119,11 +119,11 @@ public function prepare_items() { 'hide_empty' => 0, ); - if ( ! empty( $_REQUEST['orderby'] ) ) { + if ( ! empty( $_REQUEST['orderby'] ) && is_string( $_REQUEST['orderby'] ) ) { $args['orderby'] = trim( wp_unslash( $_REQUEST['orderby'] ) ); } - if ( ! empty( $_REQUEST['order'] ) ) { + if ( ! empty( $_REQUEST['order'] ) && is_string( $_REQUEST['order'] ) ) { $args['order'] = trim( wp_unslash( $_REQUEST['order'] ) ); } diff --git a/src/wp-admin/includes/class-wp-users-list-table.php b/src/wp-admin/includes/class-wp-users-list-table.php index 46eac2064660e..856dee311c91f 100644 --- a/src/wp-admin/includes/class-wp-users-list-table.php +++ b/src/wp-admin/includes/class-wp-users-list-table.php @@ -83,9 +83,9 @@ public function ajax_user_can() { public function prepare_items() { global $role, $usersearch; - $usersearch = isset( $_REQUEST['s'] ) ? wp_unslash( trim( $_REQUEST['s'] ) ) : ''; + $usersearch = isset( $_REQUEST['s'] ) && is_string( $_REQUEST['s'] ) ? wp_unslash( trim( $_REQUEST['s'] ) ) : ''; - $role = $_REQUEST['role'] ?? ''; + $role = isset( $_REQUEST['role'] ) && is_string( $_REQUEST['role'] ) ? $_REQUEST['role'] : ''; $per_page = ( $this->is_site_users ) ? 'site_users_network_per_page' : 'users_per_page'; $users_per_page = $this->get_items_per_page( $per_page ); @@ -118,11 +118,11 @@ public function prepare_items() { $args['blog_id'] = $this->site_id; } - if ( isset( $_REQUEST['orderby'] ) ) { + if ( isset( $_REQUEST['orderby'] ) && is_string( $_REQUEST['orderby'] ) ) { $args['orderby'] = $_REQUEST['orderby']; } - if ( isset( $_REQUEST['order'] ) ) { + if ( isset( $_REQUEST['order'] ) && is_string( $_REQUEST['order'] ) ) { $args['order'] = $_REQUEST['order']; } diff --git a/src/wp-admin/includes/plugin-install.php b/src/wp-admin/includes/plugin-install.php index ffae71c5b81f6..20eeeec0deb30 100644 --- a/src/wp-admin/includes/plugin-install.php +++ b/src/wp-admin/includes/plugin-install.php @@ -314,7 +314,7 @@ function install_dashboard() { */ function install_search_form( $deprecated = true ) { $type = isset( $_REQUEST['type'] ) ? wp_unslash( $_REQUEST['type'] ) : 'term'; - $term = isset( $_REQUEST['s'] ) ? urldecode( wp_unslash( $_REQUEST['s'] ) ) : ''; + $term = isset( $_REQUEST['s'] ) && is_string( $_REQUEST['s'] ) ? urldecode( wp_unslash( $_REQUEST['s'] ) ) : ''; ?>
diff --git a/src/wp-admin/plugins.php b/src/wp-admin/plugins.php index 520bd03dee20a..964906dc74529 100644 --- a/src/wp-admin/plugins.php +++ b/src/wp-admin/plugins.php @@ -19,7 +19,7 @@ $action = $wp_list_table->current_action(); $plugin = isset( $_REQUEST['plugin'] ) ? wp_unslash( $_REQUEST['plugin'] ) : ''; -$s = isset( $_REQUEST['s'] ) ? urlencode( wp_unslash( $_REQUEST['s'] ) ) : ''; +$s = isset( $_REQUEST['s'] ) && is_string( $_REQUEST['s'] ) ? urlencode( wp_unslash( $_REQUEST['s'] ) ) : ''; // Clean up request URI from temporary args for screen options/paging uri's to work as expected. $query_args_to_remove = array( diff --git a/src/wp-admin/site-health.php b/src/wp-admin/site-health.php index 1a70e2e1dd8f1..d09c9ed412b5f 100644 --- a/src/wp-admin/site-health.php +++ b/src/wp-admin/site-health.php @@ -36,7 +36,7 @@ 'tab-count-' . count( $tabs ), ); -$current_tab = $_GET['tab'] ?? ''; +$current_tab = isset( $_GET['tab'] ) && is_string( $_GET['tab'] ) ? $_GET['tab'] : ''; $title = sprintf( // translators: %s: The currently displayed tab. @@ -206,7 +206,7 @@
'; printf( /* translators: %s: Search query. */ diff --git a/src/wp-admin/users.php b/src/wp-admin/users.php index 0af7a33c77f16..6a904b9a93063 100644 --- a/src/wp-admin/users.php +++ b/src/wp-admin/users.php @@ -869,7 +869,7 @@ search_box( __( 'Search Users' ), 'user' ); ?> - + diff --git a/src/wp-includes/class-wp-comment-query.php b/src/wp-includes/class-wp-comment-query.php index ad706ba532114..bc82e41a77841 100644 --- a/src/wp-includes/class-wp-comment-query.php +++ b/src/wp-includes/class-wp-comment-query.php @@ -843,7 +843,11 @@ protected function get_comment_ids() { } // Falsey search strings are ignored. - if ( isset( $this->query_vars['search'] ) && strlen( $this->query_vars['search'] ) ) { + if ( + isset( $this->query_vars['search'] ) && + is_scalar( $this->query_vars['search'] ) && + strlen( $this->query_vars['search'] ) + ) { $search_sql = $this->get_search_sql( $this->query_vars['search'], array( 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_author_IP', 'comment_content' ) diff --git a/tests/phpunit/tests/comment/query.php b/tests/phpunit/tests/comment/query.php index ad025ee100b5e..b47a8b75ca74a 100644 --- a/tests/phpunit/tests/comment/query.php +++ b/tests/phpunit/tests/comment/query.php @@ -2641,6 +2641,21 @@ public function test_search_null_should_be_ignored() { $this->assertStringNotContainsString( 'comment_author LIKE', $q->request ); } + /** + * @ticket 66432 + * + * @covers WP_Comment_Query::query + */ + public function test_search_array_should_be_ignored() { + $q = new WP_Comment_Query(); + $q->query( + array( + 'search' => array( 'foo' ), + ) + ); + $this->assertStringNotContainsString( 'comment_author LIKE', $q->request ); + } + /** * @ticket 35513 * From 973bfa3d008808b01e7d9018ec0670aa9def61c4 Mon Sep 17 00:00:00 2001 From: sarequl Date: Sun, 1 Nov 2026 14:58:30 +0000 Subject: [PATCH 2/8] Administration: Guard remaining search request values. _admin_search_query() and the post_mime_type and detached hidden fields still cast request arrays to strings, which surfaced as warnings once the earlier fatals were fixed. --- src/wp-admin/includes/class-wp-list-table.php | 4 ++-- src/wp-admin/includes/template.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wp-admin/includes/class-wp-list-table.php b/src/wp-admin/includes/class-wp-list-table.php index 3ebc26dcd8e6c..209586ebf09fb 100644 --- a/src/wp-admin/includes/class-wp-list-table.php +++ b/src/wp-admin/includes/class-wp-list-table.php @@ -424,10 +424,10 @@ public function search_box( $text, $input_id ) { if ( ! empty( $_REQUEST['order'] ) && is_string( $_REQUEST['order'] ) ) { echo ''; } - if ( ! empty( $_REQUEST['post_mime_type'] ) ) { + if ( ! empty( $_REQUEST['post_mime_type'] ) && is_string( $_REQUEST['post_mime_type'] ) ) { echo ''; } - if ( ! empty( $_REQUEST['detached'] ) ) { + if ( ! empty( $_REQUEST['detached'] ) && is_string( $_REQUEST['detached'] ) ) { echo ''; } ?> diff --git a/src/wp-admin/includes/template.php b/src/wp-admin/includes/template.php index 973e7486db141..81fbf65c7d5ce 100644 --- a/src/wp-admin/includes/template.php +++ b/src/wp-admin/includes/template.php @@ -2128,7 +2128,7 @@ function _draft_or_post_title( $post = 0 ) { * @since 2.7.0 */ function _admin_search_query() { - echo isset( $_REQUEST['s'] ) ? esc_attr( wp_unslash( $_REQUEST['s'] ) ) : ''; + echo isset( $_REQUEST['s'] ) && is_string( $_REQUEST['s'] ) ? esc_attr( wp_unslash( $_REQUEST['s'] ) ) : ''; } /** From cd64c6b6737f4c53340ec8a06fb256b1374b76eb Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 12 Nov 2026 10:16:35 -0800 Subject: [PATCH 3/8] Users: Skip non-string orderby entries in WP_User_Query. An array 'orderby' whose list entry is itself an array, as with `users.php?orderby[0][]=x`, reached the `isset()` lookup on the meta query clauses in `parse_orderby()` and threw a TypeError. Guarding in the query class covers every caller of `get_users()`, not only the Users list table. Co-Authored-By: Claude Opus 5.5 (medium effort) Claude-Session: https://claude.ai/code/session_01Tnky3LnCGk4tTNDf3wjdhe Claude-Code-Session-Id: d29ab326-6a9a-4f1d-99bf-988709d1b379 --- src/wp-includes/class-wp-user-query.php | 4 ++ tests/phpunit/tests/user/query.php | 68 +++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/src/wp-includes/class-wp-user-query.php b/src/wp-includes/class-wp-user-query.php index 61fc3edbc8ef9..7325236116ba6 100644 --- a/src/wp-includes/class-wp-user-query.php +++ b/src/wp-includes/class-wp-user-query.php @@ -993,6 +993,10 @@ public function get_total() { protected function parse_orderby( $orderby ) { global $wpdb; + if ( ! is_string( $orderby ) ) { + return ''; + } + $meta_query_clauses = $this->meta_query->get_clauses(); $_orderby = ''; diff --git a/tests/phpunit/tests/user/query.php b/tests/phpunit/tests/user/query.php index ee73f1a2cb943..77f4f1955cd08 100644 --- a/tests/phpunit/tests/user/query.php +++ b/tests/phpunit/tests/user/query.php @@ -223,6 +223,74 @@ public function test_orderby_meta_value() { $this->assertSame( $expected, $q->get_results() ); } + /** + * Tests that a non-string 'orderby' entry is skipped rather than causing a fatal error. + * + * @ticket 66432 + */ + public function test_orderby_should_skip_non_string_entries() { + $user_ids = $this->create_users_with_reversed_display_names(); + + $q = new WP_User_Query( + array( + 'include' => $user_ids, + 'orderby' => array( array( 'login' ), 'display_name' ), + 'order' => 'ASC', + 'fields' => 'ID', + ) + ); + + $this->assertSame( array_map( 'strval', array_reverse( $user_ids ) ), $q->get_results() ); + } + + /** + * Tests that 'orderby' falls back to the user login when no entry is a string. + * + * @ticket 66432 + */ + public function test_orderby_should_fall_back_to_user_login_when_no_entry_is_a_string() { + $user_ids = $this->create_users_with_reversed_display_names(); + + $q = new WP_User_Query( + array( + 'include' => $user_ids, + 'orderby' => array( array( 'display_name' ) ), + 'order' => 'ASC', + 'fields' => 'ID', + ) + ); + + $this->assertSame( array_map( 'strval', $user_ids ), $q->get_results() ); + } + + /** + * Creates users whose display names sort in the reverse order of their logins. + * + * @return int[] User IDs, in ascending order of user login. + */ + private function create_users_with_reversed_display_names(): array { + return array( + self::factory()->user->create( + array( + 'user_login' => 'orderby_a', + 'display_name' => 'Zed', + ) + ), + self::factory()->user->create( + array( + 'user_login' => 'orderby_b', + 'display_name' => 'Yan', + ) + ), + self::factory()->user->create( + array( + 'user_login' => 'orderby_c', + 'display_name' => 'Xia', + ) + ), + ); + } + /** * @ticket 27887 */ From 44ff3d92b0bae07cd0679c25ea4bfc2aaa6fe104 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 12 Nov 2026 10:17:27 -0800 Subject: [PATCH 4/8] Administration: Guard bulk action post IDs against unexpected types. On the Posts and Media screens, an array `ids` was passed to `explode()`, and on the Posts screen a scalar `post` was passed to `array_map()`. Both threw a TypeError. Only a string `ids` is now exploded, and `post` is cast to an array, with any non-numeric entry mapped to 0. Co-Authored-By: Claude Opus 5.5 (medium effort) Claude-Session: https://claude.ai/code/session_01Tnky3LnCGk4tTNDf3wjdhe Claude-Code-Session-Id: d29ab326-6a9a-4f1d-99bf-988709d1b379 --- src/wp-admin/edit.php | 9 +++++++-- src/wp-admin/upload.php | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/wp-admin/edit.php b/src/wp-admin/edit.php index 38619d492b13f..6ab063e804cc2 100644 --- a/src/wp-admin/edit.php +++ b/src/wp-admin/edit.php @@ -104,10 +104,15 @@ $doaction = 'delete'; } elseif ( isset( $_REQUEST['media'] ) ) { $post_ids = $_REQUEST['media']; - } elseif ( isset( $_REQUEST['ids'] ) ) { + } elseif ( isset( $_REQUEST['ids'] ) && is_string( $_REQUEST['ids'] ) ) { $post_ids = explode( ',', $_REQUEST['ids'] ); } elseif ( ! empty( $_REQUEST['post'] ) ) { - $post_ids = array_map( 'intval', $_REQUEST['post'] ); + $post_ids = array_map( + static function ( $post_id ): int { + return is_numeric( $post_id ) ? (int) $post_id : 0; + }, + (array) $_REQUEST['post'] + ); } if ( empty( $post_ids ) ) { diff --git a/src/wp-admin/upload.php b/src/wp-admin/upload.php index 80500bfc10dd6..eca25e2beadb2 100644 --- a/src/wp-admin/upload.php +++ b/src/wp-admin/upload.php @@ -271,7 +271,7 @@ function () { $doaction = 'delete'; } elseif ( isset( $_REQUEST['media'] ) ) { $post_ids = $_REQUEST['media']; - } elseif ( isset( $_REQUEST['ids'] ) ) { + } elseif ( isset( $_REQUEST['ids'] ) && is_string( $_REQUEST['ids'] ) ) { $post_ids = explode( ',', $_REQUEST['ids'] ); } $post_ids = array_map( 'intval', (array) $post_ids ); From 1d68298d2a0c68fc2b05dac02441781a61fc5148 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 12 Nov 2026 10:17:44 -0800 Subject: [PATCH 5/8] Plugins: Only carry string orderby and order into the search box. `WP_Plugins_List_Table::search_box()` overrides the parent method, so it missed the string checks added there. An array `orderby` or `order` raised an "Array to string conversion" warning and printed `value="Array"` into the hidden input. Co-Authored-By: Claude Opus 5.5 (medium effort) Claude-Session: https://claude.ai/code/session_01Tnky3LnCGk4tTNDf3wjdhe Claude-Code-Session-Id: d29ab326-6a9a-4f1d-99bf-988709d1b379 --- src/wp-admin/includes/class-wp-plugins-list-table.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp-admin/includes/class-wp-plugins-list-table.php b/src/wp-admin/includes/class-wp-plugins-list-table.php index 53220806f8ce1..39036d56f8858 100644 --- a/src/wp-admin/includes/class-wp-plugins-list-table.php +++ b/src/wp-admin/includes/class-wp-plugins-list-table.php @@ -481,10 +481,10 @@ public function search_box( $text, $input_id ) { $input_id = $input_id . '-search-input'; - if ( ! empty( $_REQUEST['orderby'] ) ) { + if ( ! empty( $_REQUEST['orderby'] ) && is_string( $_REQUEST['orderby'] ) ) { echo ''; } - if ( ! empty( $_REQUEST['order'] ) ) { + if ( ! empty( $_REQUEST['order'] ) && is_string( $_REQUEST['order'] ) ) { echo ''; } ?> From 133700cb9915e9ea7cdf95cf789326b502f70898 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 12 Nov 2026 10:17:54 -0800 Subject: [PATCH 6/8] Administration: Skip nested orderby values in the list table search box. When `orderby` is an array, `WP_List_Table::search_box()` echoes each entry back as a hidden input. A nested array entry, such as from `orderby[title][]=x`, raised an "Array to string conversion" warning and printed `value="Array"`. Non-string entries are now skipped. Co-Authored-By: Claude Opus 5.5 (medium effort) Claude-Session: https://claude.ai/code/session_01Tnky3LnCGk4tTNDf3wjdhe Claude-Code-Session-Id: d29ab326-6a9a-4f1d-99bf-988709d1b379 --- src/wp-admin/includes/class-wp-list-table.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/wp-admin/includes/class-wp-list-table.php b/src/wp-admin/includes/class-wp-list-table.php index 209586ebf09fb..b90e911d058f2 100644 --- a/src/wp-admin/includes/class-wp-list-table.php +++ b/src/wp-admin/includes/class-wp-list-table.php @@ -415,6 +415,9 @@ public function search_box( $text, $input_id ) { if ( ! empty( $_REQUEST['orderby'] ) ) { if ( is_array( $_REQUEST['orderby'] ) ) { foreach ( $_REQUEST['orderby'] as $key => $value ) { + if ( ! is_string( $value ) ) { + continue; + } echo ''; } } else { From e8b73b4c26e743d16980b48cfb4cff33a4ed7ccc Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 12 Nov 2026 10:18:09 -0800 Subject: [PATCH 7/8] Plugins: Ignore a non-string search type on the Add Plugins screen. The search `type` was read without a string check next to the `s` reads that already had one. An array `type` raised "Array to string conversion" warnings in `selected()`. It now falls back to `term`, in both the search form and the list table. Co-Authored-By: Claude Opus 5.5 (medium effort) Claude-Session: https://claude.ai/code/session_01Tnky3LnCGk4tTNDf3wjdhe Claude-Code-Session-Id: d29ab326-6a9a-4f1d-99bf-988709d1b379 --- src/wp-admin/includes/class-wp-plugin-install-list-table.php | 2 +- src/wp-admin/includes/plugin-install.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wp-admin/includes/class-wp-plugin-install-list-table.php b/src/wp-admin/includes/class-wp-plugin-install-list-table.php index 0a5fc600d9ed3..f4636dd2bac17 100644 --- a/src/wp-admin/includes/class-wp-plugin-install-list-table.php +++ b/src/wp-admin/includes/class-wp-plugin-install-list-table.php @@ -203,7 +203,7 @@ public function prepare_items() { switch ( $tab ) { case 'search': - $type = isset( $_REQUEST['type'] ) ? wp_unslash( $_REQUEST['type'] ) : 'term'; + $type = isset( $_REQUEST['type'] ) && is_string( $_REQUEST['type'] ) ? wp_unslash( $_REQUEST['type'] ) : 'term'; $term = isset( $_REQUEST['s'] ) && is_string( $_REQUEST['s'] ) ? wp_unslash( $_REQUEST['s'] ) : ''; switch ( $type ) { diff --git a/src/wp-admin/includes/plugin-install.php b/src/wp-admin/includes/plugin-install.php index 9a28434a8ea94..7484eb9e8ca8e 100644 --- a/src/wp-admin/includes/plugin-install.php +++ b/src/wp-admin/includes/plugin-install.php @@ -313,7 +313,7 @@ function install_dashboard() { * @phpstan-param true $deprecated */ function install_search_form( $deprecated = true ) { - $type = isset( $_REQUEST['type'] ) ? wp_unslash( $_REQUEST['type'] ) : 'term'; + $type = isset( $_REQUEST['type'] ) && is_string( $_REQUEST['type'] ) ? wp_unslash( $_REQUEST['type'] ) : 'term'; $term = isset( $_REQUEST['s'] ) && is_string( $_REQUEST['s'] ) ? urldecode( wp_unslash( $_REQUEST['s'] ) ) : ''; ?> From 339aec5a740b7db4a28913c7b02d0c455f2d255f Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 12 Nov 2026 10:18:32 -0800 Subject: [PATCH 8/8] Comments: Only accept numeric start and offset in the comments list table. `WP_Comments_List_Table::prepare_items()` added the AJAX `offset` to `start` without checking either. An array, or a non-numeric string, threw an "Unsupported operand types" TypeError. Both are now used only when numeric and are cast to integers; otherwise `start` falls back to the page offset. Co-Authored-By: Claude Opus 5.5 (medium effort) Claude-Session: https://claude.ai/code/session_01Tnky3LnCGk4tTNDf3wjdhe Claude-Code-Session-Id: d29ab326-6a9a-4f1d-99bf-988709d1b379 --- src/wp-admin/includes/class-wp-comments-list-table.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wp-admin/includes/class-wp-comments-list-table.php b/src/wp-admin/includes/class-wp-comments-list-table.php index 294f9d4f9c3ac..824d4585631e7 100644 --- a/src/wp-admin/includes/class-wp-comments-list-table.php +++ b/src/wp-admin/includes/class-wp-comments-list-table.php @@ -132,14 +132,14 @@ public function prepare_items() { $page = $this->get_pagenum(); - if ( isset( $_REQUEST['start'] ) ) { - $start = $_REQUEST['start']; + if ( isset( $_REQUEST['start'] ) && is_numeric( $_REQUEST['start'] ) ) { + $start = (int) $_REQUEST['start']; } else { $start = ( $page - 1 ) * $comments_per_page; } - if ( $doing_ajax && isset( $_REQUEST['offset'] ) ) { - $start += $_REQUEST['offset']; + if ( $doing_ajax && isset( $_REQUEST['offset'] ) && is_numeric( $_REQUEST['offset'] ) ) { + $start += (int) $_REQUEST['offset']; } $status_map = array(