@@ -226,6 +226,14 @@ 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+
229237 return $ errors ;
230238 }
231239
@@ -249,6 +257,114 @@ function edit_user( $user_id = 0 ) {
249257 return $ user_id ;
250258}
251259
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+
319+ /**
320+ * Gets the form fields responsible for a failed user update.
321+ *
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.
325+ *
326+ * @since 7.1.0
327+ * @access private
328+ *
329+ * @param WP_Error $errors Validation errors from `edit_user()`.
330+ * @return string[] Array of field names that should be excluded from the save.
331+ */
332+ function _get_edit_user_error_fields ( $ errors ) {
333+ $ error_fields = array ();
334+
335+ foreach ( $ errors ->get_error_codes () as $ code ) {
336+ $ mapped_fields = array ();
337+
338+ foreach ( $ errors ->get_all_error_data ( $ code ) as $ data ) {
339+ if ( is_array ( $ data ) && ! empty ( $ data ['form-field ' ] ) ) {
340+ $ mapped_fields [] = $ data ['form-field ' ];
341+ }
342+ }
343+
344+ if ( empty ( $ mapped_fields ) ) {
345+ switch ( $ code ) {
346+ case 'invalid_username ' :
347+ case 'user_login ' :
348+ $ mapped_fields [] = 'user_login ' ;
349+ break ;
350+ case 'nickname ' :
351+ $ mapped_fields [] = 'nickname ' ;
352+ break ;
353+ }
354+ }
355+
356+ if ( empty ( $ mapped_fields ) ) {
357+ return array ();
358+ }
359+
360+ foreach ( $ mapped_fields as $ field ) {
361+ $ error_fields [ $ field ] = true ;
362+ }
363+ }
364+
365+ return array_keys ( $ error_fields );
366+ }
367+
252368/**
253369 * Fetch a filtered list of user roles that the current user is
254370 * allowed to edit.
0 commit comments