Skip to content

PHP 8 fatal in WPUM_Avatars::set_default_avatar() when $id_or_email is WP_Comment or WP_User #443

Description

@richardscholtens

Affected versions: at least 2.9.12 and 2.9.16 (current as of this filing).
PHP: 8.0+ (confirmed on 8.3.13)

Summary

includes/admin/class-wpum-avatars.php line 205 builds a transient cache key by concatenating $id_or_email directly:

$cache_key = 'wpum_default_avatar_' . $id_or_email;

Per the function's own docblock, $id_or_email is mixed — WordPress passes a WP_User, WP_Comment, user ID, or email depending on the caller. On PHP 8, concatenating an object without __toString() throws:

Uncaught Error: Object of class WP_Comment could not be converted to string

…fatalling the request (HTTP 500, white screen).

Reproduction

Any front-end single-post page whose theme calls comments_template() and which has ≥1 approved comment. Walker_Comment invokes get_avatar(), which applies the get_avatar_url filter with a WP_Comment object.

Stack trace:

#0 class-wpum-avatars.php(205): WPUM_Avatars->set_default_avatar('https://secure...', Object(WP_Comment), Array)
#1 class-wp-hook.php(324):  WP_Hook->apply_filters(...)
#3 link-template.php(4571): apply_filters('get_avatar_url', ...)
#5 pluggable.php(3157):     get_avatar_url(Object(WP_Comment), Array)
#6 class-walker-comment.php(427): get_avatar(Object(WP_Comment), 40)
...
#11 theme/comments.php(17): wp_list_comments(Array)
#13 theme/single.php(60):   comments_template()

The same fatal also fires with Object(WP_User) when author avatars are rendered via the same filter.

Suggested fix

Derive a safe scalar key from any input type:

if ( is_object( $id_or_email ) ) {
    if ( ! empty( $id_or_email->comment_ID ) ) {
        $key_part = 'c' . $id_or_email->comment_ID;
    } elseif ( ! empty( $id_or_email->ID ) ) {
        $key_part = 'u' . $id_or_email->ID;
    } elseif ( ! empty( $id_or_email->user_email ) ) {
        $key_part = 'e' . md5( $id_or_email->user_email );
    } else {
        $key_part = md5( serialize( $id_or_email ) );
    }
} else {
    $key_part = (string) $id_or_email;
}
$cache_key = 'wpum_default_avatar_' . $key_part;

Side note

The early-return guard

if ( is_admin() && ( ! isset( $pagenow ) || 'options-discussion.php' !== $pagenow ) ) {
    return $url;
}

…means the expensive path (including a synchronous wp_remote_head() to Gravatar) runs on every front-end avatar URL resolution. That's a separate performance concern worth addressing alongside the fix.

Workaround currently in use

We replaced the filter callback from an mu-plugin on one affected production site. Happy to share the mu-plugin file if useful.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions