Skip to content

Commit 5a9766f

Browse files
rootroot
authored andcommitted
Unit test cases fixes
1 parent 021a33a commit 5a9766f

File tree

3 files changed

+258
-73
lines changed

3 files changed

+258
-73
lines changed

src/wp-admin/includes/user.php

Lines changed: 116 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -226,14 +226,6 @@ function edit_user( $user_id = 0 ) {
226226
do_action_ref_array( 'user_profile_update_errors', array( &$errors, $update, &$user ) );
227227

228228
if ( $errors->has_errors() ) {
229-
if ( $update ) {
230-
$updated_user_id = _edit_user_partial_update( $user, $errors );
231-
232-
if ( is_wp_error( $updated_user_id ) ) {
233-
$errors->merge_from( $updated_user_id );
234-
}
235-
}
236-
237229
return $errors;
238230
}
239231

@@ -257,77 +249,17 @@ function edit_user( $user_id = 0 ) {
257249
return $user_id;
258250
}
259251

260-
/**
261-
* Updates a user with the valid data from a failed profile submission.
262-
*
263-
* When an existing user update fails due to field-specific validation errors,
264-
* the remaining valid fields can still be saved so they are not lost on the
265-
* subsequent page load.
266-
*
267-
* @since 7.1.0
268-
* @access private
269-
*
270-
* @param stdClass $user User data prepared for `wp_update_user()`.
271-
* @param WP_Error $errors Validation errors from `edit_user()`.
272-
* @return int|WP_Error User ID on success, WP_Error on failure, or 0 if no partial update was attempted.
273-
*/
274-
function _edit_user_partial_update( $user, $errors ) {
275-
$error_fields = _get_edit_user_error_fields( $errors );
276-
277-
if ( ! $error_fields ) {
278-
return 0;
279-
}
280-
281-
foreach ( $error_fields as $field ) {
282-
$property = _get_edit_user_property( $field );
283-
284-
if ( property_exists( $user, $property ) ) {
285-
unset( $user->$property );
286-
}
287-
}
288-
289-
if ( 1 === count( get_object_vars( $user ) ) ) {
290-
return 0;
291-
}
292-
293-
return wp_update_user( $user );
294-
}
295-
296-
/**
297-
* Maps edit-user form fields to the properties stored by `wp_update_user()`.
298-
*
299-
* @since 7.1.0
300-
* @access private
301-
*
302-
* @param string $field Form field name.
303-
* @return string User property name.
304-
*/
305-
function _get_edit_user_property( $field ) {
306-
switch ( $field ) {
307-
case 'email':
308-
return 'user_email';
309-
case 'url':
310-
return 'user_url';
311-
case 'pass1':
312-
case 'pass2':
313-
return 'user_pass';
314-
default:
315-
return $field;
316-
}
317-
}
318-
319252
/**
320253
* Gets the form fields responsible for a failed user update.
321254
*
322-
* Only errors that can be mapped back to a specific submitted field are
323-
* considered safe for partial saving. Any unknown error aborts the partial
324-
* update so the submission remains fully blocked.
255+
* These fields are excluded when rebuilding the edit form from submitted data
256+
* after a failed update.
325257
*
326258
* @since 7.1.0
327259
* @access private
328260
*
329261
* @param WP_Error $errors Validation errors from `edit_user()`.
330-
* @return string[] Array of field names that should be excluded from the save.
262+
* @return string[] Array of field names that should not be repopulated.
331263
*/
332264
function _get_edit_user_error_fields( $errors ) {
333265
$error_fields = array();
@@ -365,6 +297,119 @@ function _get_edit_user_error_fields( $errors ) {
365297
return array_keys( $error_fields );
366298
}
367299

300+
/**
301+
* Retrieves user data for the edit screen and overlays safe submitted values.
302+
*
303+
* When an existing user update fails validation, the edit and profile screens
304+
* should preserve submitted values without writing them to the database. Fields
305+
* that produced errors are left at their stored values, and password fields are
306+
* never repopulated.
307+
*
308+
* @since 7.1.0
309+
* @access private
310+
*
311+
* @param int $user_id User ID.
312+
* @param WP_Error $errors Validation errors from `edit_user()`.
313+
* @return WP_User|false WP_User object on success, false on failure.
314+
*/
315+
function _get_user_to_edit_from_post( $user_id, $errors ) {
316+
$user = get_user_to_edit( $user_id );
317+
$is_profile_page = defined( 'IS_PROFILE_PAGE' ) && IS_PROFILE_PAGE;
318+
319+
if ( ! $user || ! is_wp_error( $errors ) || ! $errors->has_errors() ) {
320+
return $user;
321+
}
322+
323+
$error_fields = array_flip( _get_edit_user_error_fields( $errors ) );
324+
$properties = array(
325+
'first_name' => 'first_name',
326+
'last_name' => 'last_name',
327+
'nickname' => 'nickname',
328+
'display_name' => 'display_name',
329+
'email' => 'user_email',
330+
'url' => 'user_url',
331+
'description' => 'description',
332+
'locale' => 'locale',
333+
'admin_color' => 'admin_color',
334+
);
335+
336+
foreach ( $properties as $field => $property ) {
337+
if ( isset( $error_fields[ $field ] ) || ! isset( $_POST[ $field ] ) ) {
338+
continue;
339+
}
340+
341+
$user->$property = wp_unslash( $_POST[ $field ] );
342+
}
343+
344+
foreach ( wp_get_user_contact_methods( $user ) as $method => $label ) {
345+
if ( isset( $error_fields[ $method ] ) || ! isset( $_POST[ $method ] ) ) {
346+
continue;
347+
}
348+
349+
$user->$method = wp_unslash( $_POST[ $method ] );
350+
}
351+
352+
if ( ! isset( $error_fields['rich_editing'] ) ) {
353+
$user->rich_editing = isset( $_POST['rich_editing'] ) && 'false' === $_POST['rich_editing'] ? 'false' : 'true';
354+
}
355+
356+
if ( ! isset( $error_fields['syntax_highlighting'] ) ) {
357+
$user->syntax_highlighting = isset( $_POST['syntax_highlighting'] ) && 'false' === $_POST['syntax_highlighting'] ? 'false' : 'true';
358+
}
359+
360+
if ( ! isset( $error_fields['comment_shortcuts'] ) ) {
361+
$user->comment_shortcuts = isset( $_POST['comment_shortcuts'] ) && 'true' === $_POST['comment_shortcuts'] ? 'true' : '';
362+
}
363+
364+
if ( ! isset( $error_fields['admin_bar_front'] ) ) {
365+
$user->show_admin_bar_front = isset( $_POST['admin_bar_front'] ) ? 'true' : 'false';
366+
}
367+
368+
if ( ! isset( $error_fields['use_ssl'] ) ) {
369+
$user->use_ssl = ! empty( $_POST['use_ssl'] ) ? 1 : 0;
370+
}
371+
372+
if ( ! isset( $error_fields['role'] )
373+
&& ! $is_profile_page
374+
&& ! is_network_admin()
375+
&& isset( $_POST['role'] )
376+
&& current_user_can( 'promote_user', $user_id )
377+
) {
378+
$role = sanitize_text_field( wp_unslash( $_POST['role'] ) );
379+
$editable_roles = get_editable_roles();
380+
381+
if ( '' === $role || ! empty( $editable_roles[ $role ] ) ) {
382+
$user->roles = $role ? array( $role ) : array();
383+
}
384+
}
385+
386+
return $user;
387+
}
388+
389+
/**
390+
* Filters user options for the edit screen when repopulating submitted values.
391+
*
392+
* @since 7.1.0
393+
* @access private
394+
*
395+
* @param mixed $result Value for the user's option.
396+
* @param string $option Name of the option being retrieved.
397+
* @param WP_User $user WP_User object of the user whose option is being retrieved.
398+
* @return mixed Filtered user option value.
399+
*/
400+
function _get_user_edit_form_posted_option( $result, $option, $user ) {
401+
global $_wp_user_edit_posted_options;
402+
403+
if ( empty( $_wp_user_edit_posted_options['user_id'] )
404+
|| (int) $_wp_user_edit_posted_options['user_id'] !== (int) $user->ID
405+
|| ! isset( $_wp_user_edit_posted_options['options'][ $option ] )
406+
) {
407+
return $result;
408+
}
409+
410+
return $_wp_user_edit_posted_options['options'][ $option ];
411+
}
412+
368413
/**
369414
* Fetch a filtered list of user roles that the current user is
370415
* allowed to edit.

src/wp-admin/user-edit.php

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,11 @@
189189

190190
// Intentional fall-through to display $errors.
191191
default:
192-
$profile_user = get_user_to_edit( $user_id );
192+
if ( isset( $errors ) && is_wp_error( $errors ) ) {
193+
$profile_user = _get_user_to_edit_from_post( $user_id, $errors );
194+
} else {
195+
$profile_user = get_user_to_edit( $user_id );
196+
}
193197

194198
if ( ! current_user_can( 'edit_user', $user_id ) ) {
195199
wp_die( __( 'Sorry, you are not allowed to edit this user.' ) );
@@ -336,6 +340,16 @@
336340
<th scope="row"><?php _e( 'Administration Color Scheme' ); ?></th>
337341
<td>
338342
<?php
343+
if ( isset( $errors ) && is_wp_error( $errors ) && isset( $profile_user->admin_color ) ) {
344+
$_wp_user_edit_posted_options = array(
345+
'user_id' => $profile_user->ID,
346+
'options' => array(
347+
'admin_color' => $profile_user->admin_color,
348+
),
349+
);
350+
add_filter( 'get_user_option_admin_color', '_get_user_edit_form_posted_option', 10, 3 );
351+
}
352+
339353
/**
340354
* Fires in the 'Administration Color Scheme' section of the user editing screen.
341355
*
@@ -348,6 +362,11 @@
348362
* @param int $user_id The user ID.
349363
*/
350364
do_action( 'admin_color_scheme_picker', $user_id );
365+
366+
if ( isset( $_wp_user_edit_posted_options ) ) {
367+
remove_filter( 'get_user_option_admin_color', '_get_user_edit_form_posted_option', 10 );
368+
unset( $_wp_user_edit_posted_options );
369+
}
351370
?>
352371
</td>
353372
</tr>
@@ -369,8 +388,13 @@
369388
<tr class="show-admin-bar user-admin-bar-front-wrap">
370389
<th scope="row"><?php _e( 'Toolbar' ); ?></th>
371390
<td>
391+
<?php
392+
$show_admin_bar_front = isset( $errors ) && is_wp_error( $errors )
393+
? 'true' === $profile_user->show_admin_bar_front
394+
: _get_admin_bar_pref( 'front', $profile_user->ID );
395+
?>
372396
<label for="admin_bar_front">
373-
<input name="admin_bar_front" type="checkbox" id="admin_bar_front" value="1"<?php checked( _get_admin_bar_pref( 'front', $profile_user->ID ) ); ?> />
397+
<input name="admin_bar_front" type="checkbox" id="admin_bar_front" value="1"<?php checked( $show_admin_bar_front ); ?> />
374398
<?php _e( 'Show Toolbar when viewing site' ); ?>
375399
</label><br />
376400
</td>
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
<?php
2+
3+
/**
4+
* @group admin
5+
* @group user
6+
*
7+
* @covers ::_get_user_to_edit_from_post
8+
* @covers ::_get_edit_user_error_fields
9+
* @covers ::_get_user_edit_form_posted_option
10+
*/
11+
class Admin_Includes_User_GetUserToEditFromPost_Test extends WP_UnitTestCase {
12+
13+
/**
14+
* Cleans up globals after each test.
15+
*/
16+
public function tear_down() {
17+
$_POST = array();
18+
unset( $GLOBALS['_wp_user_edit_posted_options'] );
19+
20+
parent::tear_down();
21+
}
22+
23+
/**
24+
* Tests that safe submitted values are repopulated without changing stored data.
25+
*
26+
* @ticket 26962
27+
*/
28+
public function test_get_user_to_edit_from_post_repopulates_safe_values_without_saving() {
29+
$administrator_id = self::factory()->user->create(
30+
array(
31+
'role' => 'administrator',
32+
)
33+
);
34+
$user_id = self::factory()->user->create(
35+
array(
36+
'role' => 'subscriber',
37+
'user_email' => 'stored@example.com',
38+
'first_name' => 'Stored',
39+
'description' => 'Stored bio',
40+
'rich_editing' => 'true',
41+
)
42+
);
43+
44+
wp_set_current_user( $administrator_id );
45+
update_user_option( $user_id, 'admin_color', 'modern' );
46+
update_user_option( $user_id, 'show_admin_bar_front', 'false' );
47+
48+
$_POST = array(
49+
'first_name' => 'Posted',
50+
'display_name' => 'Posted Display Name',
51+
'email' => 'not-an-email-address',
52+
'description' => 'Posted bio',
53+
'rich_editing' => 'false',
54+
'syntax_highlighting' => 'false',
55+
'comment_shortcuts' => 'true',
56+
'admin_bar_front' => '1',
57+
'admin_color' => 'midnight',
58+
'use_ssl' => '1',
59+
'role' => 'editor',
60+
'pass1' => 'secret-pass',
61+
'pass2' => '',
62+
);
63+
64+
$errors = new WP_Error();
65+
$errors->add( 'invalid_email', 'Invalid email.', array( 'form-field' => 'email' ) );
66+
$errors->add( 'pass', 'Passwords do not match.', array( 'form-field' => 'pass1' ) );
67+
68+
$user = _get_user_to_edit_from_post( $user_id, $errors );
69+
70+
$this->assertSame( 'Posted', $user->first_name );
71+
$this->assertSame( 'Posted Display Name', $user->display_name );
72+
$this->assertSame( 'Stored bio', get_userdata( $user_id )->description );
73+
$this->assertSame( 'Posted bio', $user->description );
74+
$this->assertSame( 'stored@example.com', $user->user_email );
75+
$this->assertSame( 'false', $user->rich_editing );
76+
$this->assertSame( 'false', $user->syntax_highlighting );
77+
$this->assertSame( 'true', $user->comment_shortcuts );
78+
$this->assertSame( 'true', $user->show_admin_bar_front );
79+
$this->assertSame( '1', $user->use_ssl );
80+
$this->assertSame( array( 'editor' ), $user->roles );
81+
82+
$stored_user = get_userdata( $user_id );
83+
84+
$this->assertSame( 'Stored', $stored_user->first_name );
85+
$this->assertSame( 'Stored bio', $stored_user->description );
86+
$this->assertSame( array( 'subscriber' ), $stored_user->roles );
87+
$this->assertSame( 'modern', get_user_option( 'admin_color', $user_id ) );
88+
$this->assertFalse( _get_admin_bar_pref( 'front', $user_id ) );
89+
}
90+
91+
/**
92+
* Tests that posted user options are only used for the matching user.
93+
*
94+
* @ticket 26962
95+
*/
96+
public function test_get_user_edit_form_posted_option_only_overrides_matching_user() {
97+
$user_id = self::factory()->user->create();
98+
$other_user_id = self::factory()->user->create();
99+
100+
$GLOBALS['_wp_user_edit_posted_options'] = array(
101+
'user_id' => $user_id,
102+
'options' => array(
103+
'admin_color' => 'midnight',
104+
),
105+
);
106+
107+
$this->assertSame(
108+
'midnight',
109+
_get_user_edit_form_posted_option( 'modern', 'admin_color', get_userdata( $user_id ) )
110+
);
111+
$this->assertSame(
112+
'modern',
113+
_get_user_edit_form_posted_option( 'modern', 'admin_color', get_userdata( $other_user_id ) )
114+
);
115+
}
116+
}

0 commit comments

Comments
 (0)