Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion includes/admin/class-wpum-avatars.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ public function __construct() {

if ( wpum_get_option( 'custom_avatars' ) ) {
add_action( 'carbon_fields_register_fields', array( $this, 'avatar_field' ) );
add_filter( 'get_avatar_url', array( $this, 'set_avatar_url' ), 10, 3 );

// Set user uploaded avatar a higher priority than the default avatar.
add_filter( 'get_avatar_url', array( $this, 'set_avatar_url' ), 11, 3 );
}

if ( ! wpum_get_option( 'disable_profile_cover' ) ) {
Expand Down
120 changes: 120 additions & 0 deletions tests/wpunit/Avatars/AvatarPriorityTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php
/**
* Tests for the WPUM_Avatars filter priority fix (#411).
*
* When both a custom avatar and a site-wide default avatar are configured,
* the custom avatar filter (set_avatar_url) must run AFTER the default avatar
* filter (set_default_avatar) so the custom avatar takes precedence.
*
* @see https://github.com/WPUserManager/wp-user-manager/pull/411
Comment on lines +3 to +9

Copilot AI Feb 26, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test header hard-codes PR references (#411 / pull/411). This is brittle and can become incorrect when cherry-picked or backported; prefer referencing the tracked issue (e.g., #403) or describing the behavior without linking to a specific PR number.

Suggested change
* Tests for the WPUM_Avatars filter priority fix (#411).
*
* When both a custom avatar and a site-wide default avatar are configured,
* the custom avatar filter (set_avatar_url) must run AFTER the default avatar
* filter (set_default_avatar) so the custom avatar takes precedence.
*
* @see https://github.com/WPUserManager/wp-user-manager/pull/411
* Tests for the WPUM_Avatars filter priority behavior.
*
* When both a custom avatar and a site-wide default avatar are configured,
* the custom avatar filter (set_avatar_url) must run AFTER the default avatar
* filter (set_default_avatar) so the custom avatar takes precedence.

Copilot uses AI. Check for mistakes.
*/

require_once dirname( __DIR__ ) . '/WPUMTestCase.php';

class AvatarPriorityTest extends WPUMTestCase {

public function _setUp() {
parent::_setUp();

// Enable the custom_avatars and default_avatar WPUM options so both
// code paths in the WPUM_Avatars constructor are entered.
global $wpum_options;
if ( ! is_array( $wpum_options ) ) {
$wpum_options = array();
}
$wpum_options['custom_avatars'] = true;
$wpum_options['default_avatar'] = 'http://example.com/default-avatar.jpg';

// Instantiate a fresh WPUM_Avatars so both filters are registered.
new WPUM_Avatars();
}

public function _tearDown() {
// Remove filters that our fresh instance added, so they don't leak
// into other tests.
remove_all_filters( 'get_avatar_url' );

Comment on lines +32 to +36

Copilot AI Feb 26, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove_all_filters( 'get_avatar_url' ) removes every callback on that hook (including core/other plugin/test callbacks), which can cause cross-test pollution. Prefer removing only the WPUM_Avatars callbacks you added (e.g., iterate current callbacks and remove_filter where the callable object is a WPUM_Avatars instance), or snapshot/restore the prior callbacks in _tearDown().

Copilot uses AI. Check for mistakes.
global $wpum_options;
if ( is_array( $wpum_options ) ) {
unset( $wpum_options['custom_avatars'], $wpum_options['default_avatar'] );
}

parent::_tearDown();
}

/**
* Verify the WPUM_Avatars class exists and is loadable.
*/
public function test_wpum_avatars_class_exists() {
$this->assertTrue( class_exists( 'WPUM_Avatars' ), 'WPUM_Avatars class should be loaded.' );
}

/**
* The set_avatar_url callback (custom avatar) must be registered at priority 11.
*/
public function test_set_avatar_url_registered_at_priority_11() {
$priority = has_filter( 'get_avatar_url', array( $this->get_avatars_instance(), 'set_avatar_url' ) );

$this->assertNotFalse( $priority, 'set_avatar_url should be registered on get_avatar_url.' );
$this->assertSame( 11, $priority, 'set_avatar_url must be registered at priority 11.' );
}

/**
* The set_default_avatar callback must be registered at priority 10 (the default).
*/
public function test_set_default_avatar_registered_at_priority_10() {
$priority = has_filter( 'get_avatar_url', array( $this->get_avatars_instance(), 'set_default_avatar' ) );

$this->assertNotFalse( $priority, 'set_default_avatar should be registered on get_avatar_url.' );
$this->assertSame( 10, $priority, 'set_default_avatar must be registered at priority 10.' );
}

/**
* The custom avatar filter priority must be strictly greater than the
* default avatar filter priority, so the custom avatar wins.
*/
public function test_custom_avatar_priority_is_higher_than_default() {
$instance = $this->get_avatars_instance();

$custom_priority = has_filter( 'get_avatar_url', array( $instance, 'set_avatar_url' ) );
$default_priority = has_filter( 'get_avatar_url', array( $instance, 'set_default_avatar' ) );

$this->assertNotFalse( $custom_priority, 'set_avatar_url should be registered.' );
$this->assertNotFalse( $default_priority, 'set_default_avatar should be registered.' );

$this->assertGreaterThan(
$default_priority,
$custom_priority,
'Custom avatar filter (set_avatar_url) must run after the default avatar filter (set_default_avatar).'
);
}

/**
* Helper: return the WPUM_Avatars instance that was created in _setUp.
*
* We retrieve it from the filter registry rather than storing a reference,
* so the test truly reflects what WordPress sees.
*
* @return WPUM_Avatars
*/
private function get_avatars_instance() {
global $wp_filter;

if ( ! isset( $wp_filter['get_avatar_url'] ) ) {
$this->fail( 'get_avatar_url filter is not registered.' );
}

foreach ( $wp_filter['get_avatar_url']->callbacks as $priority => $callbacks ) {
foreach ( $callbacks as $callback ) {
if (
is_array( $callback['function'] ) &&
$callback['function'][0] instanceof WPUM_Avatars
) {
return $callback['function'][0];
Comment on lines +92 to +113

Copilot AI Feb 26, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_avatars_instance() reaches into the $wp_filter internals (WP_Hook::$callbacks) and returns the first WPUM_Avatars instance it finds. This is brittle across WP versions and also doesn’t guarantee it returns the instance created in _setUp() if another WPUM_Avatars instance is already registered. Consider storing the instance created in _setUp() on the test class (and using has_filter on that), or making the helper explicitly select the most recently added callback.

Copilot uses AI. Check for mistakes.
}
}
}

$this->fail( 'Could not find a WPUM_Avatars instance in the get_avatar_url filter.' );
}
}