Skip to content

Commit 64c9e1e

Browse files
committed
Release 2.9.18
2 parents b10a593 + 743147b commit 64c9e1e

10 files changed

Lines changed: 476 additions & 9 deletions

File tree

includes/actions.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -262,10 +262,11 @@ function wpum_restrict_account_page() {
262262
*/
263263
function wpum_display_account_page_content() {
264264

265-
$active_tab = get_query_var( 'tab' );
266265
$tabs = wpum_get_account_page_tabs();
266+
$active_tab = get_query_var( 'tab' );
267267

268-
if ( empty( $active_tab ) ) {
268+
// Validate against registered tabs to prevent path traversal / LFI.
269+
if ( empty( $active_tab ) || ! isset( $tabs[ $active_tab ] ) ) {
269270
$active_tab = key( $tabs );
270271
}
271272

@@ -516,7 +517,7 @@ function wpum_register_multiple_roles_field() {
516517
$user_id = filter_input( INPUT_GET, 'user_id', FILTER_VALIDATE_INT );
517518
$profileuser = isset( $user_id ) ? get_user_by( 'id', $user_id ) : false;
518519

519-
if ( ! $profileuser && 'user-new.php' !== $pagenow ) {
520+
if ( ! $profileuser && ! in_array( $pagenow, array( 'user-new.php', 'user-edit.php' ), true ) ) {
520521
return;
521522
}
522523

includes/admin/class-wpum-avatars.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ public function __construct() {
3636

3737
if ( wpum_get_option( 'custom_avatars' ) ) {
3838
add_action( 'carbon_fields_register_fields', array( $this, 'avatar_field' ) );
39-
add_filter( 'get_avatar_url', array( $this, 'set_avatar_url' ), 10, 3 );
39+
40+
// Set user uploaded avatar a higher priority than the default avatar.
41+
add_filter( 'get_avatar_url', array( $this, 'set_avatar_url' ), 11, 3 );
4042
}
4143

4244
if ( ! wpum_get_option( 'disable_profile_cover' ) ) {

includes/emails/class-wpum-emails-customizer-scripts.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ public function customize_controls() {
9292
'sections' => $sections,
9393
);
9494
wp_localize_script( 'wpum-email-customize-controls', 'wpumCustomizeControls', $js_variables );
95+
96+
// This is a workaround to ensure that the tinymce editor is initialized in a block theme.
97+
if ( function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() ) {
98+
do_action( 'admin_print_footer_scripts' );
99+
}
95100
}
96101
}
97102

includes/functions.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -951,9 +951,15 @@ function wpum_get_profile_tab_url( $user, $tab ) {
951951
* @return string
952952
*/
953953
function wpum_get_active_profile_tab() {
954-
$first_tab = key( wpum_get_registered_profile_tabs() );
954+
$registered = wpum_get_registered_profile_tabs();
955+
$first_tab = key( $registered );
955956
$profile_tab = get_query_var( 'tab', $first_tab );
956957

958+
// Validate against registered tabs to prevent path traversal / LFI.
959+
if ( ! isset( $registered[ $profile_tab ] ) ) {
960+
$profile_tab = $first_tab;
961+
}
962+
957963
return $profile_tab;
958964
}
959965

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "wp-user-manager",
33
"title": "WP User Manager",
44
"description": "Plugin",
5-
"version": "2.9.17",
5+
"version": "2.9.18",
66
"homepage": "https://wpusermanager.com",
77
"author": {
88
"name": "WP User Manager",

readme.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Tested up to: 6.9
88
Requires PHP: 7.4
99
License: GPLv2 or later
1010
License URI: http://www.gnu.org/licenses/gpl-2.0.html
11-
Stable Tag: 2.9.17
11+
Stable Tag: 2.9.18
1212

1313
The most customizable profiles & community builder WordPress plugin with front-end login, registration, profile customization and content restriction.
1414

@@ -134,6 +134,13 @@ Please note that using WPUM and the mentioned add-ons does NOT guarantee complia
134134

135135
== Changelog ==
136136

137+
= 2.9.18 (30th May 2026) =
138+
139+
- Security: Hardened profile tab input validation (thanks to Yat via Wordfence for responsible disclosure)
140+
- Fix: Custom avatar not shown when a default avatar is also configured
141+
- Fix: Multiple roles not saving on the admin edit user page
142+
- Fix: Email content editor for 'Delete Inactive Accounts' not working with block themes
143+
137144
= 2.9.17 (11th May 2026) =
138145

139146
- Security: Hardened file field input validation during registration (thanks to endy via Patchstack for responsible disclosure)
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?php
2+
/**
3+
* Tests for the WPUM_Avatars filter priority fix (#411).
4+
*
5+
* When both a custom avatar and a site-wide default avatar are configured,
6+
* the custom avatar filter (set_avatar_url) must run AFTER the default avatar
7+
* filter (set_default_avatar) so the custom avatar takes precedence.
8+
*
9+
* @see https://github.com/WPUserManager/wp-user-manager/pull/411
10+
*/
11+
12+
require_once dirname( __DIR__ ) . '/WPUMTestCase.php';
13+
14+
class AvatarPriorityTest extends WPUMTestCase {
15+
16+
public function _setUp() {
17+
parent::_setUp();
18+
19+
// Enable the custom_avatars and default_avatar WPUM options so both
20+
// code paths in the WPUM_Avatars constructor are entered.
21+
global $wpum_options;
22+
if ( ! is_array( $wpum_options ) ) {
23+
$wpum_options = array();
24+
}
25+
$wpum_options['custom_avatars'] = true;
26+
$wpum_options['default_avatar'] = 'http://example.com/default-avatar.jpg';
27+
28+
// Instantiate a fresh WPUM_Avatars so both filters are registered.
29+
new WPUM_Avatars();
30+
}
31+
32+
public function _tearDown() {
33+
// Remove filters that our fresh instance added, so they don't leak
34+
// into other tests.
35+
remove_all_filters( 'get_avatar_url' );
36+
37+
global $wpum_options;
38+
if ( is_array( $wpum_options ) ) {
39+
unset( $wpum_options['custom_avatars'], $wpum_options['default_avatar'] );
40+
}
41+
42+
parent::_tearDown();
43+
}
44+
45+
/**
46+
* Verify the WPUM_Avatars class exists and is loadable.
47+
*/
48+
public function test_wpum_avatars_class_exists() {
49+
$this->assertTrue( class_exists( 'WPUM_Avatars' ), 'WPUM_Avatars class should be loaded.' );
50+
}
51+
52+
/**
53+
* The set_avatar_url callback (custom avatar) must be registered at priority 11.
54+
*/
55+
public function test_set_avatar_url_registered_at_priority_11() {
56+
$priority = has_filter( 'get_avatar_url', array( $this->get_avatars_instance(), 'set_avatar_url' ) );
57+
58+
$this->assertNotFalse( $priority, 'set_avatar_url should be registered on get_avatar_url.' );
59+
$this->assertSame( 11, $priority, 'set_avatar_url must be registered at priority 11.' );
60+
}
61+
62+
/**
63+
* The set_default_avatar callback must be registered at priority 10 (the default).
64+
*/
65+
public function test_set_default_avatar_registered_at_priority_10() {
66+
$priority = has_filter( 'get_avatar_url', array( $this->get_avatars_instance(), 'set_default_avatar' ) );
67+
68+
$this->assertNotFalse( $priority, 'set_default_avatar should be registered on get_avatar_url.' );
69+
$this->assertSame( 10, $priority, 'set_default_avatar must be registered at priority 10.' );
70+
}
71+
72+
/**
73+
* The custom avatar filter priority must be strictly greater than the
74+
* default avatar filter priority, so the custom avatar wins.
75+
*/
76+
public function test_custom_avatar_priority_is_higher_than_default() {
77+
$instance = $this->get_avatars_instance();
78+
79+
$custom_priority = has_filter( 'get_avatar_url', array( $instance, 'set_avatar_url' ) );
80+
$default_priority = has_filter( 'get_avatar_url', array( $instance, 'set_default_avatar' ) );
81+
82+
$this->assertNotFalse( $custom_priority, 'set_avatar_url should be registered.' );
83+
$this->assertNotFalse( $default_priority, 'set_default_avatar should be registered.' );
84+
85+
$this->assertGreaterThan(
86+
$default_priority,
87+
$custom_priority,
88+
'Custom avatar filter (set_avatar_url) must run after the default avatar filter (set_default_avatar).'
89+
);
90+
}
91+
92+
/**
93+
* Helper: return the WPUM_Avatars instance that was created in _setUp.
94+
*
95+
* We retrieve it from the filter registry rather than storing a reference,
96+
* so the test truly reflects what WordPress sees.
97+
*
98+
* @return WPUM_Avatars
99+
*/
100+
private function get_avatars_instance() {
101+
global $wp_filter;
102+
103+
if ( ! isset( $wp_filter['get_avatar_url'] ) ) {
104+
$this->fail( 'get_avatar_url filter is not registered.' );
105+
}
106+
107+
foreach ( $wp_filter['get_avatar_url']->callbacks as $priority => $callbacks ) {
108+
foreach ( $callbacks as $callback ) {
109+
if (
110+
is_array( $callback['function'] ) &&
111+
$callback['function'][0] instanceof WPUM_Avatars
112+
) {
113+
return $callback['function'][0];
114+
}
115+
}
116+
}
117+
118+
$this->fail( 'Could not find a WPUM_Avatars instance in the get_avatar_url filter.' );
119+
}
120+
}

0 commit comments

Comments
 (0)