diff --git a/includes/admin/class-admin.php b/includes/admin/class-admin.php index 1d5ce2a2..d503b5cf 100644 --- a/includes/admin/class-admin.php +++ b/includes/admin/class-admin.php @@ -767,10 +767,10 @@ if ( ! class_exists( 'um\admin\Admin' ) ) { 'sanitize' => 'absint', ), '_min' => array( - 'sanitize' => 'absint', + 'sanitize' => 'empty_int', ), '_max' => array( - 'sanitize' => 'absint', + 'sanitize' => 'empty_int', ), '_min_chars' => array( 'sanitize' => 'absint', @@ -1360,6 +1360,9 @@ if ( ! class_exists( 'um\admin\Admin' ) ) { case 'int': $sanitized[ $k ] = (int) $v; break; + case 'empty_int': + $sanitized[ $k ] = ( '' !== $v ) ? (int) $v : ''; + break; case 'bool': $sanitized[ $k ] = (bool) $v; break; @@ -1393,6 +1396,13 @@ if ( ! class_exists( 'um\admin\Admin' ) ) { $sanitized[ $k ] = absint( $v ); } break; + case 'empty_absint': + if ( is_array( $v ) ) { + $sanitized[ $k ] = array_map( 'absint', $v ); + } else { + $sanitized[ $k ] = ( '' !== $v ) ? absint( $v ) : ''; + } + break; } } diff --git a/includes/admin/core/class-admin-settings.php b/includes/admin/core/class-admin-settings.php index 237fb878..0b3e42a0 100644 --- a/includes/admin/core/class-admin-settings.php +++ b/includes/admin/core/class-admin-settings.php @@ -676,6 +676,12 @@ if ( ! class_exists( 'um\admin\core\Admin_Settings' ) ) { 'reset_require_strongpass' => array( 'sanitize' => 'bool', ), + 'password_min_chars' => array( + 'sanitize' => 'absint', + ), + 'password_max_chars' => array( + 'sanitize' => 'absint', + ), 'profile_noindex' => array( 'sanitize' => 'bool', ), @@ -1048,6 +1054,20 @@ if ( ! class_exists( 'um\admin\core\Admin_Settings' ) ) { 'label' => __( 'Require a strong password? (when user resets password only)', 'ultimate-member' ), 'tooltip' => __( 'Enable or disable a strong password rules on password reset and change procedure', 'ultimate-member' ), ), + array( + 'id' => 'password_min_chars', + 'type' => 'number', + 'label' => __( 'Password minimum length', 'ultimate-member' ), + 'tooltip' => __( 'If you want to enable a minimum number of characters to be in password. User password field in the UM forms has own settings for that. Leave empty to use default value 8', 'ultimate-member' ), + 'size' => 'small', + ), + array( + 'id' => 'password_max_chars', + 'type' => 'number', + 'label' => __( 'Password maximum length', 'ultimate-member' ), + 'tooltip' => __( 'If you want to enable a maximum number of characters to be in password. User password field in the UM forms has own settings for that. Leave empty to use default value 30', 'ultimate-member' ), + 'size' => 'small', + ), array( 'id' => 'profile_noindex', 'type' => 'select', diff --git a/includes/class-config.php b/includes/class-config.php index e8ccfc92..ab3fa770 100644 --- a/includes/class-config.php +++ b/includes/class-config.php @@ -523,6 +523,8 @@ if ( ! class_exists( 'um\Config' ) ) { 'use_um_gravatar_default_builtin_image' => 'default', 'use_um_gravatar_default_image' => 0, 'reset_require_strongpass' => 0, + 'password_min_chars' => 8, + 'password_max_chars' => 30, 'account_tab_password' => 1, 'account_tab_privacy' => 1, 'account_tab_notifications' => 1, diff --git a/includes/class-functions.php b/includes/class-functions.php index 4fd75217..3a770725 100644 --- a/includes/class-functions.php +++ b/includes/class-functions.php @@ -391,5 +391,20 @@ if ( ! class_exists( 'UM_Functions' ) ) { } } + + /** + * Replace the first match in the string, alternative for the `str_replace()` function + * + * @param string $search + * @param string $replace + * @param string $subject + * + * @return string + */ + function str_replace_first( $search, $replace, $subject ) { + $search = '/' . preg_quote( $search, '/' ) . '/'; + return preg_replace( $search, $replace, $subject, 1 ); + } + } } diff --git a/includes/core/class-password.php b/includes/core/class-password.php index 852dd02c..b6e0605b 100644 --- a/includes/core/class-password.php +++ b/includes/core/class-password.php @@ -543,12 +543,17 @@ if ( ! class_exists( 'um\core\Password' ) ) { if ( UM()->options()->get( 'reset_require_strongpass' ) ) { - if ( strlen( utf8_decode( $args['user_password'] ) ) < 8 ) { - UM()->form()->add_error( 'user_password', __( 'Your password must contain at least 8 characters', 'ultimate-member' ) ); + $min_length = UM()->options()->get( 'password_min_chars' ); + $min_length = ! empty( $min_length ) ? $min_length : 8; + $max_length = UM()->options()->get( 'password_max_chars' ); + $max_length = ! empty( $max_length ) ? $max_length : 30; + + if ( mb_strlen( $args['user_password'] ) < $min_length ) { + UM()->form()->add_error( 'user_password', sprintf( __( 'Your password must contain at least %d characters', 'ultimate-member' ), $min_length ) ); } - if ( strlen( utf8_decode( $args['user_password'] ) ) > 30 ) { - UM()->form()->add_error( 'user_password', __( 'Your password must contain less than 30 characters', 'ultimate-member' ) ); + if ( mb_strlen( $args['user_password'] ) > $max_length ) { + UM()->form()->add_error( 'user_password', sprintf( __( 'Your password must contain less than %d characters', 'ultimate-member' ), $max_length ) ); } if ( ! UM()->validation()->strong_pass( $args['user_password'] ) ) { diff --git a/includes/core/class-user.php b/includes/core/class-user.php index 00ef054c..6488f035 100644 --- a/includes/core/class-user.php +++ b/includes/core/class-user.php @@ -15,6 +15,14 @@ if ( ! class_exists( 'um\core\User' ) ) { class User { + /** + * Data that we will set before the update profile to compare it after update + * + * @var null + */ + public $previous_data = null; + + /** * User constructor. */ diff --git a/includes/core/um-actions-account.php b/includes/core/um-actions-account.php index 1e4feb6a..93126416 100644 --- a/includes/core/um-actions-account.php +++ b/includes/core/um-actions-account.php @@ -82,12 +82,17 @@ function um_submit_account_errors_hook( $args ) { } if ( UM()->options()->get( 'account_require_strongpass' ) ) { - if ( strlen( utf8_decode( $args['user_password'] ) ) < 8 ) { - UM()->form()->add_error( 'user_password', __( 'Your password must contain at least 8 characters', 'ultimate-member' ) ); + $min_length = UM()->options()->get( 'password_min_chars' ); + $min_length = ! empty( $min_length ) ? $min_length : 8; + $max_length = UM()->options()->get( 'password_max_chars' ); + $max_length = ! empty( $max_length ) ? $max_length : 30; + + if ( mb_strlen( $args['user_password'] ) < $min_length ) { + UM()->form()->add_error( 'user_password', sprintf( __( 'Your password must contain at least %d characters', 'ultimate-member' ), $min_length ) ); } - if ( strlen( utf8_decode( $args['user_password'] ) ) > 30 ) { - UM()->form()->add_error( 'user_password', __( 'Your password must contain less than 30 characters', 'ultimate-member' ) ); + if ( mb_strlen( $args['user_password'] ) > $max_length ) { + UM()->form()->add_error( 'user_password', sprintf( __( 'Your password must contain less than %d characters', 'ultimate-member' ), $max_length ) ); } if ( ! UM()->validation()->strong_pass( $args['user_password'] ) ) { @@ -370,8 +375,24 @@ function um_submit_account_details( $args ) { */ do_action( 'um_account_pre_update_profile', $changes, $user_id ); - UM()->user()->update_profile( $changes ); + if ( isset( $changes['first_name'] ) || isset( $changes['last_name'] ) || isset( $changes['nickname'] ) ) { + $user = get_userdata( $user_id ); + if ( ! empty( $user ) && ! is_wp_error( $user ) ) { + UM()->user()->previous_data['display_name'] = $user->display_name; + if ( isset( $changes['first_name'] ) ) { + UM()->user()->previous_data['first_name'] = $user->first_name; + } + if ( isset( $changes['last_name'] ) ) { + UM()->user()->previous_data['last_name'] = $user->last_name; + } + if ( isset( $changes['nickname'] ) ) { + UM()->user()->previous_data['nickname'] = $user->nickname; + } + } + } + + UM()->user()->update_profile( $changes ); if ( UM()->account()->is_secure_enabled() ) { update_user_meta( $user_id, 'um_account_secure_fields', array() ); @@ -497,7 +518,7 @@ add_action( 'um_before_account_notifications', 'um_before_account_notifications' * @param array $changes An array of fields values. */ function um_after_user_account_updated_permalink( $user_id, $changes ) { - if ( isset( $changes['first_name'] ) && isset( $changes['last_name'] ) ) { + if ( isset( $changes['first_name'] ) || isset( $changes['last_name'] ) ) { do_action( 'um_update_profile_full_name', $user_id, $changes ); } } diff --git a/includes/core/um-actions-form.php b/includes/core/um-actions-form.php index 40214488..ac20fdcf 100644 --- a/includes/core/um-actions-form.php +++ b/includes/core/um-actions-form.php @@ -595,7 +595,7 @@ function um_submit_form_errors_hook_( $args ) { } if ( isset( $array['min_chars'] ) && $array['min_chars'] > 0 ) { - if ( $args[ $key ] && strlen( utf8_decode( $args[ $key ] ) ) < $array['min_chars'] ) { + if ( $args[ $key ] && mb_strlen( $args[ $key ] ) < $array['min_chars'] ) { if ( empty( $array['label'] ) ) { UM()->form()->add_error( $key, sprintf( __( 'This field must contain at least %s characters', 'ultimate-member' ), $array['min_chars'] ) ); } else { @@ -605,7 +605,7 @@ function um_submit_form_errors_hook_( $args ) { } if ( isset( $array['max_chars'] ) && $array['max_chars'] > 0 ) { - if ( $args[ $key ] && strlen( utf8_decode( $args[ $key ] ) ) > $array['max_chars'] ) { + if ( $args[ $key ] && mb_strlen( $args[ $key ] ) > $array['max_chars'] ) { if ( empty( $array['label'] ) ) { UM()->form()->add_error( $key, sprintf( __( 'This field must contain less than %s characters', 'ultimate-member' ), $array['max_chars'] ) ); } else { @@ -895,7 +895,7 @@ function um_submit_form_errors_hook_( $args ) { $profile_show_bio = UM()->options()->get( 'profile_show_bio' ); if ( $profile_show_bio ) { - if ( strlen( utf8_decode( str_replace( array( "\r\n", "\n", "\r\t", "\t" ), ' ', $args['description'] ) ) ) > $max_chars && $max_chars ) { + if ( mb_strlen( str_replace( array( "\r\n", "\n", "\r\t", "\t" ), ' ', $args['description'] ) ) > $max_chars && $max_chars ) { UM()->form()->add_error( 'description', sprintf( __( 'Your user description must contain less than %s characters', 'ultimate-member' ), $max_chars ) ); } } @@ -921,4 +921,4 @@ function um_invalid_nonce_redirect_url( $url ) { return $url; } add_filter( 'um_login_invalid_nonce_redirect_url', 'um_invalid_nonce_redirect_url', 10, 1 ); -add_filter( 'um_register_invalid_nonce_redirect_url', 'um_invalid_nonce_redirect_url', 10, 1 ); \ No newline at end of file +add_filter( 'um_register_invalid_nonce_redirect_url', 'um_invalid_nonce_redirect_url', 10, 1 ); diff --git a/includes/core/um-actions-profile.php b/includes/core/um-actions-profile.php index 92ccf3e5..25bb9b18 100644 --- a/includes/core/um-actions-profile.php +++ b/includes/core/um-actions-profile.php @@ -462,8 +462,27 @@ function um_user_edit_profile( $args ) { */ $to_update = apply_filters( 'um_user_pre_updating_profile_array', $to_update, $user_id ); - if ( is_array( $to_update ) ) { + + if ( isset( $to_update['first_name'] ) || isset( $to_update['last_name'] ) || isset( $to_update['nickname'] ) ) { + $user = get_userdata( $user_id ); + if ( ! empty( $user ) && ! is_wp_error( $user ) ) { + UM()->user()->previous_data['display_name'] = $user->display_name; + + if ( isset( $to_update['first_name'] ) ) { + UM()->user()->previous_data['first_name'] = $user->first_name; + } + + if ( isset( $to_update['last_name'] ) ) { + UM()->user()->previous_data['last_name'] = $user->last_name; + } + + if ( isset( $to_update['nickname'] ) ) { + UM()->user()->previous_data['nickname'] = $user->nickname; + } + } + } + UM()->user()->update_profile( $to_update ); /** * UM hook diff --git a/includes/core/um-actions-register.php b/includes/core/um-actions-register.php index e9a6afc0..9081cf43 100644 --- a/includes/core/um-actions-register.php +++ b/includes/core/um-actions-register.php @@ -350,7 +350,8 @@ function um_submit_form_register( $args ) { $unique_userID = uniqid(); - if ( empty( $user_login ) || strlen( $user_login ) > 30 && ! is_email( $user_login ) ) { + // see dbDelta and WP native DB structure user_login varchar(60) + if ( empty( $user_login ) || mb_strlen( $user_login ) > 60 && ! is_email( $user_login ) ) { $user_login = 'user' . $unique_userID; while ( username_exists( $user_login ) ) { $unique_userID = uniqid(); diff --git a/includes/core/um-actions-save-profile.php b/includes/core/um-actions-save-profile.php index 2a1a6232..fb2775d6 100644 --- a/includes/core/um-actions-save-profile.php +++ b/includes/core/um-actions-save-profile.php @@ -40,12 +40,48 @@ function um_update_profile_full_name( $user_id, $changes ) { break; case 'nickname': $update_name = get_user_meta( $user_id, 'nickname', true ); + break; + case 'default': + $user = get_userdata( $user_id ); + + if ( ! empty( $user ) && ! is_wp_error( $user ) ) { + if ( ! empty( UM()->user()->previous_data['display_name'] ) ) { + if ( isset( UM()->user()->previous_data['first_name'] ) ) { + $fname = get_user_meta( $user_id, 'first_name', true ); + $update_name = UM()->str_replace_first( UM()->user()->previous_data['first_name'], $fname, UM()->user()->previous_data['display_name'] ); + } + + if ( isset( UM()->user()->previous_data['last_name'] ) ) { + $lname = get_user_meta( $user_id, 'last_name', true ); + if ( isset( $update_name ) ) { + $update_name = UM()->str_replace_first( UM()->user()->previous_data['last_name'], $lname, $update_name ); + } else { + $update_name = UM()->str_replace_first( UM()->user()->previous_data['last_name'], $lname, UM()->user()->previous_data['display_name'] ); + } + } + + if ( isset( UM()->user()->previous_data['nickname'] ) ) { + $nickname = get_user_meta( $user_id, 'nickname', true ); + if ( isset( $update_name ) ) { + $update_name = UM()->str_replace_first( UM()->user()->previous_data['nickname'], $nickname, $update_name ); + } else { + $update_name = UM()->str_replace_first( UM()->user()->previous_data['nickname'], $nickname, UM()->user()->previous_data['display_name'] ); + } + } + + // unset if the display name is the same + if ( isset( $update_name ) && $update_name === UM()->user()->previous_data['display_name'] ) { + unset( $update_name ); + } + } + } + break; } if ( isset( $update_name ) ) { - $arr_user = array( 'ID' => $user_id, 'display_name' => $update_name ); + $arr_user = array( 'ID' => $user_id, 'display_name' => $update_name ); $return = wp_update_user( $arr_user ); if ( is_wp_error( $return ) ) { @@ -54,7 +90,7 @@ function um_update_profile_full_name( $user_id, $changes ) { } - if ( isset( $changes['first_name'] ) && isset( $changes['last_name'] ) ) { + if ( isset( $changes['first_name'] ) || isset( $changes['last_name'] ) ) { $user = get_userdata( $user_id ); if ( ! empty( $user ) && ! is_wp_error( $user ) ) { $full_name = $user->display_name; @@ -67,4 +103,4 @@ function um_update_profile_full_name( $user_id, $changes ) { // regenerate slug UM()->user()->generate_profile_slug( $user_id ); } -add_action( 'um_update_profile_full_name', 'um_update_profile_full_name', 10, 2 ); \ No newline at end of file +add_action( 'um_update_profile_full_name', 'um_update_profile_full_name', 10, 2 ); diff --git a/includes/core/um-filters-profile.php b/includes/core/um-filters-profile.php index be4395b3..94a30568 100644 --- a/includes/core/um-filters-profile.php +++ b/includes/core/um-filters-profile.php @@ -66,7 +66,7 @@ function um_dynamic_user_profile_title( $title, $id = '' ) { return $title; } - return ( strlen( $title ) !== strlen( utf8_decode( $title ) ) ) ? $title : utf8_encode( $title ); + return ( strlen( $title ) !== mb_strlen( $title ) ) ? $title : utf8_encode( $title ); } add_filter( 'the_title', 'um_dynamic_user_profile_title', 100000, 2 ); @@ -158,4 +158,4 @@ function um_change_profile_photo_label( $fields ) { } return $fields; } -add_filter( 'um_predefined_fields_hook', 'um_change_profile_photo_label', 10, 1 ); \ No newline at end of file +add_filter( 'um_predefined_fields_hook', 'um_change_profile_photo_label', 10, 1 ); diff --git a/includes/um-short-functions.php b/includes/um-short-functions.php index 092a21b9..4388bae4 100644 --- a/includes/um-short-functions.php +++ b/includes/um-short-functions.php @@ -14,7 +14,7 @@ * @return string */ function um_trim_string( $s, $length = 20 ) { - $s = strlen( $s ) > $length ? substr( $s, 0, $length ) . "..." : $s; + $s = mb_strlen( $s ) > $length ? substr( $s, 0, $length ) . "..." : $s; return $s; } @@ -2448,7 +2448,11 @@ function um_user( $data, $attrs = null ) { foreach ( $fields as $field ) { if ( um_profile( $field ) ) { - $name .= um_profile( $field ) . ' '; + + $field_value = maybe_unserialize( um_profile( $field ) ); + $field_value = is_array( $field_value ) ? implode( ',', $field_value ) : $field_value; + + $name .= $field_value . ' '; } elseif ( um_user( $field ) && $field != 'display_name' && $field != 'full_name' ) { $name .= um_user( $field ) . ' '; } diff --git a/readme.txt b/readme.txt index f467fb76..fcb51faf 100644 --- a/readme.txt +++ b/readme.txt @@ -159,11 +159,12 @@ The plugin works with popular caching plugins by automatically excluding Ultimat -= 2.2.5: September 21, 2021 = += 2.2.5: September 22, 2021 = * Enhancements: - Added: Ability to insert SoundCloud track URL instead of Track ID + - Added: Settings for the password min/max length (Ultimate Member > Settings > Users) * Bugfixes: @@ -172,6 +173,8 @@ The plugin works with popular caching plugins by automatically excluding Ultimat - Fixed: Restriction settings fields conditions on wp-admin screens - Fixed: Account `user_login` field validation removed as redundant. The field is disabled and not used in code flow - Fixed: `display_name` data update after First + Last name fields edition on the Account page + - Fixed: Password length validation on the Account page and Password Reset page + - Fixed: Using custom fields from multi-select dropdowns for user's `Display Name`. Unserialized custom field's values = 2.2.4: August 27, 2021 =