From b2d1fc2efe4c5a8de7e0a1ae2c3ffdf4693e4d93 Mon Sep 17 00:00:00 2001 From: Mukesh Panchal Date: Wed, 4 Nov 2026 14:58:15 +0530 Subject: [PATCH 1/4] Twenty Twenty: Use `absint()` for the custom logo dimensions. `twentytwenty_get_custom_logo()` passed the logo width and height through `esc_attr()`, then divided them by 2 for retina logos. `esc_attr()` returns a string, so PHPStan reported `binaryOp.invalid` for each division. `wp_get_attachment_image_src()` returns integer dimensions, so `absint()` keeps them as integers and still sanitizes a filtered value. The generated HTML is unchanged for every valid dimension. This change removes the matching `binaryOp.invalid` baseline entry. See #65817. Co-Authored-By: Claude Opus 5.5 --- src/wp-content/themes/twentytwenty/functions.php | 4 ++-- tests/phpstan/baselines/binaryOp.invalid.neon | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/wp-content/themes/twentytwenty/functions.php b/src/wp-content/themes/twentytwenty/functions.php index b92ff1a1bd2b7..222330d11292a 100644 --- a/src/wp-content/themes/twentytwenty/functions.php +++ b/src/wp-content/themes/twentytwenty/functions.php @@ -321,8 +321,8 @@ function twentytwenty_get_custom_logo( $html ) { if ( $logo ) { // For clarity. - $logo_width = esc_attr( $logo[1] ); - $logo_height = esc_attr( $logo[2] ); + $logo_width = absint( $logo[1] ); + $logo_height = absint( $logo[2] ); // If the retina logo setting is active, reduce the width/height by half. if ( get_theme_mod( 'retina_logo', false ) ) { diff --git a/tests/phpstan/baselines/binaryOp.invalid.neon b/tests/phpstan/baselines/binaryOp.invalid.neon index 167f159cffb34..b27216f660c5c 100644 --- a/tests/phpstan/baselines/binaryOp.invalid.neon +++ b/tests/phpstan/baselines/binaryOp.invalid.neon @@ -18,11 +18,6 @@ parameters: ignoreErrors: - - - message: '#^Binary operation "/" between string and 2 results in an error\.$#' - identifier: binaryOp.invalid - count: 2 - path: ../../../src/wp-content/themes/twentytwenty/functions.php - message: '#^Binary operation "\+" between array\\|WP_Comment\>\|int\<1, max\> and 1 results in an error\.$#' identifier: binaryOp.invalid From d1cc4e6c851f3ff94b1132058f0e2dda7ecfd536 Mon Sep 17 00:00:00 2001 From: Mukesh Panchal Date: Wed, 4 Nov 2026 14:59:18 +0530 Subject: [PATCH 2/4] Docs: Mark `count` as required in the `get_page_of_comment_query_args` filter. `get_page_of_comment()` sets `'count' => true` and adds 1 to the result of the comment query to calculate the page number. The filter documented `count` as an optional `bool`. PHPStan therefore could not tell that `WP_Comment_Query::query()` returns an integer here, and reported `binaryOp.invalid` for the addition. If a filter set `count` to `false`, the addition would throw a `TypeError`. This change documents `count` as `true` and marks it `Required.`, so the conditional return type of `WP_Comment_Query::query()` resolves to an integer. It also removes the matching `binaryOp.invalid` baseline entry. There is no runtime change. See #65817. Co-Authored-By: Claude Opus 5.5 --- src/wp-includes/comment.php | 4 ++-- tests/phpstan/baselines/binaryOp.invalid.neon | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index b6847266eb162..8afeee7441387 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -1214,8 +1214,8 @@ function get_page_of_comment( $comment_id, $args = array() ) { * (trackbacks and pingbacks), or 'all'. Default 'all'. * @type int $post_id ID of the post. * @type string $fields Comment fields to return. - * @type bool $count Whether to return a comment count (true) or array - * of comment objects (false). + * @type true $count Required. Whether to return a comment count. Must be + * true, because the page number is calculated from it. * @type string $status Comment status. * @type int $parent Parent ID of comment to retrieve children of. * @type array $date_query Date query clauses to limit comments by. See WP_Date_Query. diff --git a/tests/phpstan/baselines/binaryOp.invalid.neon b/tests/phpstan/baselines/binaryOp.invalid.neon index b27216f660c5c..f5b0fa89ece32 100644 --- a/tests/phpstan/baselines/binaryOp.invalid.neon +++ b/tests/phpstan/baselines/binaryOp.invalid.neon @@ -18,11 +18,6 @@ parameters: ignoreErrors: - - - message: '#^Binary operation "\+" between array\\|WP_Comment\>\|int\<1, max\> and 1 results in an error\.$#' - identifier: binaryOp.invalid - count: 1 - path: ../../../src/wp-includes/comment.php - message: '#^Binary operation "\+" between string and int results in an error\.$#' identifier: binaryOp.invalid From ab952114c5c733e4b52f5d8420612d02fbadd156 Mon Sep 17 00:00:00 2001 From: Mukesh Panchal Date: Wed, 4 Nov 2026 14:59:45 +0530 Subject: [PATCH 3/4] Users: Parse the password reset request time as an integer. `check_password_reset_key()` splits the stored activation key on `:` and adds the expiration duration to the first part. `explode()` returns strings, so PHPStan reported `binaryOp.invalid` for the addition. Core creates the key as `time() . ':' . wp_fast_hash( $key )`, so the first part is a Unix timestamp. This change casts it to an integer before the addition. The result is the same for every key that core creates. For a corrupted key with a non-numeric prefix, the addition used to throw a `TypeError` on PHP 8. It now gives an expiration time in the past, so the function returns its existing `expired_key` error. This change also deletes the now empty `binaryOp.invalid` baseline and removes its `includes` line from `phpstan.neon.dist`. See #65817. Co-Authored-By: Claude Opus 5.5 --- phpstan.neon.dist | 1 - src/wp-includes/user.php | 2 +- tests/phpstan/baselines/binaryOp.invalid.neon | 25 ------------------- 3 files changed, 1 insertion(+), 27 deletions(-) delete mode 100644 tests/phpstan/baselines/binaryOp.invalid.neon diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 655bb9bbc807c..1d23fc757f9f2 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -25,7 +25,6 @@ includes: - tests/phpstan/baselines/argument.type.neon - tests/phpstan/baselines/argument.unresolvableType.neon - tests/phpstan/baselines/assign.propertyType.neon - - tests/phpstan/baselines/binaryOp.invalid.neon - tests/phpstan/baselines/booleanAnd.alwaysFalse.neon - tests/phpstan/baselines/booleanAnd.leftAlwaysTrue.neon - tests/phpstan/baselines/booleanNot.alwaysTrue.neon diff --git a/src/wp-includes/user.php b/src/wp-includes/user.php index 02a412b291a8e..3d49059cecf0f 100644 --- a/src/wp-includes/user.php +++ b/src/wp-includes/user.php @@ -3236,7 +3236,7 @@ function check_password_reset_key( if ( str_contains( $user->user_activation_key, ':' ) ) { list( $pass_request_time, $pass_key ) = explode( ':', $user->user_activation_key, 2 ); - $expiration_time = $pass_request_time + $expiration_duration; + $expiration_time = (int) $pass_request_time + $expiration_duration; } else { $pass_key = $user->user_activation_key; $expiration_time = false; diff --git a/tests/phpstan/baselines/binaryOp.invalid.neon b/tests/phpstan/baselines/binaryOp.invalid.neon deleted file mode 100644 index f5b0fa89ece32..0000000000000 --- a/tests/phpstan/baselines/binaryOp.invalid.neon +++ /dev/null @@ -1,25 +0,0 @@ -# PHPStan baseline for the `binaryOp.invalid` errors in WordPress core. -# -# https://phpstan.org/error-identifiers/binaryOp.invalid -# -# Each entry is scoped to a single file and carries an exact occurrence count, -# so that a new instance is reported as a new error rather than being absorbed -# silently. Fixing an occurrence therefore means decrementing or removing its -# entry here as part of the same change. -# -# The goal is to empty this file and delete it, along with the `includes` entry -# for it in phpstan.neon.dist. -# -# Generated by `composer phpstan:baselines`. Do not edit by hand; regenerate with -# -# composer phpstan:baselines -- --identifier=binaryOp.invalid -# -# which reruns the analysis with this file suppressed so the errors surface again. - -parameters: - ignoreErrors: - - - message: '#^Binary operation "\+" between string and int results in an error\.$#' - identifier: binaryOp.invalid - count: 1 - path: ../../../src/wp-includes/user.php From 21b33166a53de970b629dc90452a2d81f4eec66e Mon Sep 17 00:00:00 2001 From: Mukesh Panchal Date: Wed, 4 Nov 2026 15:19:40 +0530 Subject: [PATCH 4/4] Tests: Cover a password reset key with a non-numeric timestamp. `check_password_reset_key()` now casts the timestamp part of a stored key to an integer. For a key with a valid hash and a non-numeric timestamp, the function returns the `expired_key` error. Before the cast, the same key threw a `TypeError` on PHP 8. This test covers that case, so the error cannot come back unnoticed. See #65817. Co-Authored-By: Claude Opus 5.5 --- tests/phpunit/tests/auth.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/phpunit/tests/auth.php b/tests/phpunit/tests/auth.php index a419c93299a36..28de89c77864c 100644 --- a/tests/phpunit/tests/auth.php +++ b/tests/phpunit/tests/auth.php @@ -865,6 +865,30 @@ public function test_expired_user_activation_key_is_rejected() { $this->assertSame( 'expired_key', $check->get_error_code() ); } + /** + * @ticket 65817 + */ + public function test_user_activation_key_with_non_numeric_timestamp_is_rejected() { + global $wpdb; + + $key = wp_generate_password( 20, false ); + $wpdb->update( + $wpdb->users, + array( + 'user_activation_key' => 'not-a-timestamp:' . wp_fast_hash( $key ), + ), + array( + 'ID' => $this->user->ID, + ) + ); + clean_user_cache( $this->user ); + + // A key with a valid hash but a malformed timestamp should be rejected as expired. + $check = check_password_reset_key( $key, $this->user->user_login ); + $this->assertWPError( $check ); + $this->assertSame( 'expired_key', $check->get_error_code() ); + } + /** * @ticket 32429 */