mirror of
https://github.com/10h30/ultimatemember.git
synced 2026-07-17 13:43:38 +09:00
Merge pull request #1461 from ultimatemember/development/2.8.x
Version 2.8.4
This commit is contained in:
@@ -44,7 +44,7 @@ GNU Version 2 or Any Later Version
|
||||
|
||||
### IMPORTANT: PLEASE UPDATE THE PLUGIN TO AT LEAST VERSION 2.6.7 IMMEDIATELY. VERSION 2.6.7 PATCHES SECURITY PRIVILEGE ESCALATION VULNERABILITY. PLEASE SEE [THIS ARTICLE](https://docs.ultimatemember.com/article/1866-security-incident-update-and-recommended-actions) FOR MORE INFORMATION
|
||||
|
||||
[Official Release Version: 2.8.3](https://github.com/ultimatemember/ultimatemember/releases/tag/2.8.3).
|
||||
[Official Release Version: 2.8.4](https://github.com/ultimatemember/ultimatemember/releases/tag/2.8.4).
|
||||
|
||||
## Changelog
|
||||
|
||||
|
||||
+3379
File diff suppressed because it is too large
Load Diff
@@ -166,12 +166,12 @@ $premium['profile-tabs'] = array(
|
||||
'desc' => 'Add custom tabs to profiles',
|
||||
);
|
||||
|
||||
//$premium['stripe'] = array(
|
||||
// 'url' => 'https://ultimatemember.com/extensions/stripe/',
|
||||
// 'img' => 'stripe.png',
|
||||
// 'name' => 'Stripe',
|
||||
// 'desc' => 'Sell paid memberships to access your website via Stripe subscriptions',
|
||||
//);
|
||||
$premium['stripe'] = array(
|
||||
'url' => 'https://ultimatemember.com/extensions/stripe/',
|
||||
'img' => 'stripe.png',
|
||||
'name' => 'Stripe',
|
||||
'desc' => 'Sell paid memberships to access your website via Stripe subscriptions',
|
||||
);
|
||||
|
||||
$free['jobboardwp'] = array(
|
||||
'url' => 'https://wordpress.org/plugins/um-jobboardwp',
|
||||
|
||||
@@ -623,7 +623,10 @@ if ( ! class_exists( 'um\core\Fields' ) ) {
|
||||
* @return string
|
||||
*/
|
||||
public function show_error( $key ) {
|
||||
return UM()->form()->errors[ $key ];
|
||||
if ( empty( UM()->form()->errors ) ) {
|
||||
return '';
|
||||
}
|
||||
return array_key_exists( $key, UM()->form()->errors ) ? UM()->form()->errors[ $key ] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -634,7 +637,10 @@ if ( ! class_exists( 'um\core\Fields' ) ) {
|
||||
* @return string
|
||||
*/
|
||||
public function show_notice( $key ) {
|
||||
return UM()->form()->notices[ $key ];
|
||||
if ( empty( UM()->form()->notices ) ) {
|
||||
return '';
|
||||
}
|
||||
return array_key_exists( $key, UM()->form()->notices ) ? UM()->form()->notices[ $key ] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -351,8 +351,8 @@ if ( ! class_exists( 'um\core\Mail' ) ) {
|
||||
*/
|
||||
$message = apply_filters( 'um_email_send_message_content', $message, $slug, $args );
|
||||
|
||||
add_filter( 'um_template_tags_patterns_hook', array( &$this, 'add_placeholder' ) );
|
||||
add_filter( 'um_template_tags_replaces_hook', array( &$this, 'add_replace_placeholder' ) );
|
||||
// add_filter( 'um_template_tags_patterns_hook', array( &$this, 'add_placeholder' ) );
|
||||
// add_filter( 'um_template_tags_replaces_hook', array( &$this, 'add_replace_placeholder' ) );
|
||||
|
||||
// Convert tags in email template.
|
||||
return um_convert_tags( $message, $args );
|
||||
@@ -421,8 +421,8 @@ if ( ! class_exists( 'um\core\Mail' ) ) {
|
||||
$mail_from_addr = UM()->options()->get( 'mail_from_addr' ) ? UM()->options()->get( 'mail_from_addr' ) : get_bloginfo( 'admin_email' );
|
||||
$this->headers = 'From: ' . stripslashes( $mail_from ) . ' <' . $mail_from_addr . '>' . "\r\n";
|
||||
|
||||
add_filter( 'um_template_tags_patterns_hook', array( UM()->mail(), 'add_placeholder' ) );
|
||||
add_filter( 'um_template_tags_replaces_hook', array( UM()->mail(), 'add_replace_placeholder' ) );
|
||||
add_filter( 'um_template_tags_patterns_hook', array( $this, 'add_placeholder' ) );
|
||||
add_filter( 'um_template_tags_replaces_hook', array( $this, 'add_replace_placeholder' ) );
|
||||
|
||||
/**
|
||||
* Filters email notification subject.
|
||||
|
||||
@@ -2393,8 +2393,8 @@ if ( ! class_exists( 'um\core\Member_Directory' ) ) {
|
||||
|
||||
if ( UM()->roles()->um_current_user_can( 'edit', $user_id ) ) {
|
||||
$actions['um-editprofile'] = array(
|
||||
'title' => __( 'Edit Profile', 'ultimate-member' ),
|
||||
'url' => um_edit_profile_url(),
|
||||
'title' => esc_html__( 'Edit Profile', 'ultimate-member' ),
|
||||
'url' => um_edit_profile_url(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2425,8 +2425,8 @@ if ( ! class_exists( 'um\core\Member_Directory' ) ) {
|
||||
$url = add_query_arg( array( 'um_action' => $id, 'uid' => $user_id ), um_get_core_page( 'user' ) );
|
||||
|
||||
$actions[ $id ] = array(
|
||||
'title' => $arr['label'],
|
||||
'url' => $url,
|
||||
'title' => esc_html( $arr['label'] ),
|
||||
'url' => esc_url( $url ),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2437,25 +2437,24 @@ if ( ! class_exists( 'um\core\Member_Directory' ) ) {
|
||||
|
||||
if ( empty( UM()->user()->cannot_edit ) ) {
|
||||
$actions['um-editprofile'] = array(
|
||||
'title' => __( 'Edit Profile', 'ultimate-member' ),
|
||||
'title' => esc_html__( 'Edit Profile', 'ultimate-member' ),
|
||||
'url' => um_edit_profile_url(),
|
||||
);
|
||||
}
|
||||
|
||||
$actions['um-myaccount'] = array(
|
||||
'title' => __( 'My Account', 'ultimate-member' ),
|
||||
'title' => esc_html__( 'My Account', 'ultimate-member' ),
|
||||
'url' => um_get_core_page( 'account' ),
|
||||
);
|
||||
|
||||
$actions['um-logout'] = array(
|
||||
'title' => __( 'Logout', 'ultimate-member' ),
|
||||
'title' => esc_html__( 'Logout', 'ultimate-member' ),
|
||||
'url' => um_get_core_page( 'logout' ),
|
||||
);
|
||||
|
||||
$actions = apply_filters( 'um_member_directory_my_user_card_actions', $actions, $user_id );
|
||||
}
|
||||
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
@@ -2472,7 +2471,7 @@ if ( ! class_exists( 'um\core\Member_Directory' ) ) {
|
||||
|
||||
$dropdown_actions = $this->build_user_actions_list( $user_id );
|
||||
|
||||
$actions = array();
|
||||
$actions = array();
|
||||
$can_edit = UM()->roles()->um_current_user_can( 'edit', $user_id );
|
||||
|
||||
// Replace hook 'um_members_just_after_name'
|
||||
@@ -2486,21 +2485,21 @@ if ( ! class_exists( 'um\core\Member_Directory' ) ) {
|
||||
$hook_after_user_name = ob_get_clean();
|
||||
|
||||
$data_array = array(
|
||||
'card_anchor' => substr( md5( $user_id ), 10, 5 ),
|
||||
'id' => $user_id,
|
||||
'role' => um_user( 'role' ),
|
||||
'account_status' => um_user( 'account_status' ),
|
||||
'account_status_name' => um_user( 'account_status_name' ),
|
||||
'cover_photo' => um_user( 'cover_photo', $this->cover_size ),
|
||||
'display_name' => um_user( 'display_name' ),
|
||||
'profile_url' => um_user_profile_url(),
|
||||
'can_edit' => $can_edit,
|
||||
'edit_profile_url' => um_edit_profile_url(),
|
||||
'avatar' => get_avatar( $user_id, $this->avatar_size ),
|
||||
'display_name_html' => um_user( 'display_name', 'html' ),
|
||||
'dropdown_actions' => $dropdown_actions,
|
||||
'hook_just_after_name' => preg_replace( '/^\s+/im', '', $hook_just_after_name ),
|
||||
'hook_after_user_name' => preg_replace( '/^\s+/im', '', $hook_after_user_name ),
|
||||
'card_anchor' => esc_html( substr( md5( $user_id ), 10, 5 ) ),
|
||||
'id' => absint( $user_id ),
|
||||
'role' => esc_html( um_user( 'role' ) ),
|
||||
'account_status' => esc_html( um_user( 'account_status' ) ),
|
||||
'account_status_name' => esc_html( um_user( 'account_status_name' ) ),
|
||||
'cover_photo' => wp_kses( um_user( 'cover_photo', $this->cover_size ), UM()->get_allowed_html( 'templates' ) ),
|
||||
'display_name' => esc_html( um_user( 'display_name' ) ),
|
||||
'profile_url' => esc_url( um_user_profile_url() ),
|
||||
'can_edit' => (bool) $can_edit,
|
||||
'edit_profile_url' => esc_url( um_edit_profile_url() ),
|
||||
'avatar' => wp_kses( get_avatar( $user_id, $this->avatar_size ), UM()->get_allowed_html( 'templates' ) ),
|
||||
'display_name_html' => wp_kses( um_user( 'display_name', 'html' ), UM()->get_allowed_html( 'templates' ) ),
|
||||
'dropdown_actions' => $dropdown_actions,
|
||||
'hook_just_after_name' => wp_kses( preg_replace( '/^\s+/im', '', $hook_just_after_name ), UM()->get_allowed_html( 'templates' ) ),
|
||||
'hook_after_user_name' => wp_kses( preg_replace( '/^\s+/im', '', $hook_after_user_name ), UM()->get_allowed_html( 'templates' ) ),
|
||||
);
|
||||
|
||||
if ( ! empty( $directory_data['show_tagline'] ) ) {
|
||||
@@ -2520,7 +2519,7 @@ if ( ! class_exists( 'um\core\Member_Directory' ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data_array[ $key ] = $value;
|
||||
$data_array[ $key ] = wp_kses( $value, UM()->get_allowed_html( 'templates' ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2551,8 +2550,8 @@ if ( ! class_exists( 'um\core\Member_Directory' ) ) {
|
||||
) );
|
||||
}
|
||||
|
||||
$data_array[ "label_{$key}" ] = __( $label, 'ultimate-member' );
|
||||
$data_array[ $key ] = $value;
|
||||
$data_array[ "label_{$key}" ] = esc_html__( $label, 'ultimate-member' );
|
||||
$data_array[ $key ] = wp_kses( $value, UM()->get_allowed_html( 'templates' ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2562,7 +2561,7 @@ if ( ! class_exists( 'um\core\Member_Directory' ) ) {
|
||||
UM()->fields()->show_social_urls();
|
||||
$social_urls = ob_get_clean();
|
||||
|
||||
$data_array['social_urls'] = $social_urls;
|
||||
$data_array['social_urls'] = wp_kses( $social_urls, UM()->get_allowed_html( 'templates' ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,20 +38,25 @@ if ( ! class_exists( 'um\core\Password' ) ) {
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
function reset_url() {
|
||||
public function reset_url() {
|
||||
static $reset_key = null;
|
||||
|
||||
$user_id = um_user( 'ID' );
|
||||
|
||||
delete_option( "um_cache_userdata_{$user_id}" );
|
||||
|
||||
//new reset password key via WordPress native field. It maybe already exists here but generated twice to make sure that emailed with a proper and fresh hash
|
||||
// New reset password key via WordPress native field. It maybe already exists here but generated twice to make sure that emailed with a proper and fresh hash.
|
||||
// But doing that only once in 1 request using static variable. Different email placeholders can use reset_url() and we have to use 1 time generated to avoid invalid keys.
|
||||
$user_data = get_userdata( $user_id );
|
||||
$key = UM()->user()->maybe_generate_password_reset_key( $user_data );
|
||||
if ( empty( $reset_key ) ) {
|
||||
$reset_key = UM()->user()->maybe_generate_password_reset_key( $user_data );
|
||||
}
|
||||
|
||||
// this link looks like WordPress native link e.g. wp-login.php?action=rp&key={hash}&login={user_login}
|
||||
$url = add_query_arg(
|
||||
array(
|
||||
'act' => 'reset_password',
|
||||
'hash' => $key,
|
||||
'hash' => $reset_key,
|
||||
'login' => $user_data->user_login,
|
||||
),
|
||||
um_get_core_page( 'password-reset' )
|
||||
@@ -490,6 +495,7 @@ if ( ! class_exists( 'um\core\Password' ) ) {
|
||||
|
||||
if ( isset( $args['user_password'] ) && empty( $args['user_password'] ) ) {
|
||||
UM()->form()->add_error( 'user_password', __( 'You must enter a new password', 'ultimate-member' ) );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( isset( $args['user_password'] ) ) {
|
||||
|
||||
@@ -968,28 +968,29 @@ if ( ! class_exists( 'um\core\Uploader' ) ) {
|
||||
/**
|
||||
* Profile photo image process
|
||||
*
|
||||
* @param array $response
|
||||
* @param array $response
|
||||
* @param string $image_path
|
||||
* @param string $src
|
||||
* @param string $key
|
||||
* @param integer $user_id
|
||||
* @param int $user_id
|
||||
* @param string $coord
|
||||
* @param array $crop
|
||||
* @param array $crop
|
||||
*
|
||||
* @since 2.0.22
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function profile_photo( $response, $image_path, $src, $key, $user_id, $coord, $crop ) {
|
||||
$sizes = UM()->options()->get( 'photo_thumb_sizes' );
|
||||
|
||||
$sizes = UM()->options()->get( 'photo_thumb_sizes' );
|
||||
$quality = UM()->options()->get( 'image_compression' );
|
||||
|
||||
$image = wp_get_image_editor( $image_path ); // Return an implementation that extends WP_Image_Editor
|
||||
|
||||
$temp_image_path = $image_path;
|
||||
//refresh image_path to make temporary image permanently after upload
|
||||
$image_path = pathinfo( $image_path, PATHINFO_DIRNAME ) . DIRECTORY_SEPARATOR . $key . '.' . pathinfo( $image_path, PATHINFO_EXTENSION );
|
||||
|
||||
// Refresh image_path to make temporary image permanently after upload
|
||||
$photo_ext = pathinfo( $image_path, PATHINFO_EXTENSION );
|
||||
$image_path = pathinfo( $image_path, PATHINFO_DIRNAME ) . DIRECTORY_SEPARATOR . $key . '.' . $photo_ext;
|
||||
|
||||
if ( ! is_wp_error( $image ) ) {
|
||||
$src_x = $crop[0];
|
||||
@@ -1004,12 +1005,16 @@ if ( ! class_exists( 'um\core\Uploader' ) ) {
|
||||
$image->resize( $max_w, $src_h );
|
||||
}
|
||||
|
||||
$image->save( $image_path );
|
||||
$save_result = $image->save( $image_path );
|
||||
|
||||
if ( is_wp_error( $save_result ) ) {
|
||||
// translators: %s is the file src.
|
||||
wp_send_json_error( sprintf( __( 'Unable to crop image file: %s', 'ultimate-member' ), $src ) );
|
||||
}
|
||||
|
||||
$image->set_quality( $quality );
|
||||
|
||||
$sizes_array = array();
|
||||
|
||||
foreach ( $sizes as $size ) {
|
||||
$sizes_array[] = array( 'width' => $size );
|
||||
}
|
||||
@@ -1020,50 +1025,49 @@ if ( ! class_exists( 'um\core\Uploader' ) ) {
|
||||
|
||||
unlink( $temp_image_path );
|
||||
|
||||
$src = str_replace( '/' . $key . '_temp.', '/' . $key . '.', $src );
|
||||
$basename = $key . '_temp.' . $photo_ext;
|
||||
$src = str_replace( '/' . $basename, '/' . $save_result['file'], $src );
|
||||
|
||||
$response['image']['source_url'] = $src;
|
||||
$response['image']['source_path'] = $image_path;
|
||||
$response['image']['filename'] = wp_basename( $image_path );
|
||||
$response['image']['source_url'] = $src;
|
||||
$response['image']['source_path'] = $save_result['path'];
|
||||
$response['image']['filename'] = $save_result['file'];
|
||||
|
||||
update_user_meta( $this->user_id, $key, wp_basename( wp_basename( $image_path ) ) );
|
||||
update_user_meta( $this->user_id, $key, $save_result['file'] );
|
||||
delete_user_meta( $this->user_id, "{$key}_metadata_temp" );
|
||||
} else {
|
||||
wp_send_json_error( esc_js( __( "Unable to crop image file: {$src}", 'ultimate-member' ) ) );
|
||||
// translators: %s is the file src.
|
||||
wp_send_json_error( sprintf( __( 'Unable to crop image file: %s', 'ultimate-member' ), $src ) );
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Cover photo image process
|
||||
*
|
||||
* @param string $src
|
||||
* @param integer $user_id
|
||||
* @param int $user_id
|
||||
* @param string $coord
|
||||
* @param array $crop
|
||||
* @param array $response
|
||||
* @param array $crop
|
||||
* @param array $response
|
||||
*
|
||||
* @since 2.0.22
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function cover_photo( $response, $image_path, $src, $key, $user_id, $coord, $crop ) {
|
||||
|
||||
$sizes = UM()->options()->get( 'cover_thumb_sizes' );
|
||||
|
||||
$sizes = UM()->options()->get( 'cover_thumb_sizes' );
|
||||
$quality = UM()->options()->get( 'image_compression' );
|
||||
|
||||
$image = wp_get_image_editor( $image_path ); // Return an implementation that extends WP_Image_Editor
|
||||
|
||||
$temp_image_path = $image_path;
|
||||
|
||||
//refresh image_path to make temporary image permanently after upload
|
||||
$image_path = pathinfo( $image_path, PATHINFO_DIRNAME ) . DIRECTORY_SEPARATOR . $key . '.' . pathinfo( $image_path, PATHINFO_EXTENSION );
|
||||
// Refresh image_path to make temporary image permanently after upload
|
||||
$photo_ext = pathinfo( $image_path, PATHINFO_EXTENSION );
|
||||
$image_path = pathinfo( $image_path, PATHINFO_DIRNAME ) . DIRECTORY_SEPARATOR . $key . '.' . $photo_ext;
|
||||
|
||||
if ( ! is_wp_error( $image ) ) {
|
||||
|
||||
$src_x = $crop[0];
|
||||
$src_y = $crop[1];
|
||||
$src_w = $crop[2];
|
||||
@@ -1076,7 +1080,12 @@ if ( ! class_exists( 'um\core\Uploader' ) ) {
|
||||
$image->resize( $max_w, $src_h );
|
||||
}
|
||||
|
||||
$image->save( $image_path );
|
||||
$save_result = $image->save( $image_path );
|
||||
|
||||
if ( is_wp_error( $save_result ) ) {
|
||||
// translators: %s is the file src.
|
||||
wp_send_json_error( sprintf( __( 'Unable to crop image file: %s', 'ultimate-member' ), $src ) );
|
||||
}
|
||||
|
||||
$image->set_quality( $quality );
|
||||
|
||||
@@ -1090,7 +1099,7 @@ if ( ! class_exists( 'um\core\Uploader' ) ) {
|
||||
|
||||
// change filenames of resized images
|
||||
foreach ( $resize as $row ) {
|
||||
$new_filename = str_replace( "x{$row['height']}" , '', $row['file'] );
|
||||
$new_filename = str_replace( "x{$row['height']}", '', $row['file'] );
|
||||
$old_filename = $row['file'];
|
||||
|
||||
rename( dirname( $image_path ) . DIRECTORY_SEPARATOR . $old_filename, dirname( $image_path ) . DIRECTORY_SEPARATOR . $new_filename );
|
||||
@@ -1098,16 +1107,18 @@ if ( ! class_exists( 'um\core\Uploader' ) ) {
|
||||
|
||||
unlink( $temp_image_path );
|
||||
|
||||
$src = str_replace( '/' . $key . '_temp.', '/' . $key . '.', $src );
|
||||
$basename = $key . '_temp.' . $photo_ext;
|
||||
$src = str_replace( '/' . $basename, '/' . $save_result['file'], $src );
|
||||
|
||||
$response['image']['source_url'] = $src;
|
||||
$response['image']['source_path'] = $image_path;
|
||||
$response['image']['filename'] = wp_basename( $image_path );
|
||||
$response['image']['source_url'] = $src;
|
||||
$response['image']['source_path'] = $save_result['path'];
|
||||
$response['image']['filename'] = $save_result['file'];
|
||||
|
||||
update_user_meta( $this->user_id, $key, wp_basename( wp_basename( $image_path ) ) );
|
||||
update_user_meta( $this->user_id, $key, $save_result['file'] );
|
||||
delete_user_meta( $this->user_id, "{$key}_metadata_temp" );
|
||||
} else {
|
||||
wp_send_json_error( esc_js( __( "Unable to crop image file: {$src}", 'ultimate-member' ) ) );
|
||||
// translators: %s is the file src.
|
||||
wp_send_json_error( sprintf( __( 'Unable to crop image file: %s', 'ultimate-member' ), $src ) );
|
||||
}
|
||||
|
||||
return $response;
|
||||
@@ -1195,15 +1206,15 @@ if ( ! class_exists( 'um\core\Uploader' ) ) {
|
||||
|
||||
$response = array(
|
||||
'image' => array(
|
||||
'source_url' => $src,
|
||||
'source_path' => $image_path,
|
||||
'filename' => wp_basename( $image_path ),
|
||||
'source_url' => $src,
|
||||
'source_path' => $image_path,
|
||||
'filename' => wp_basename( $image_path ),
|
||||
),
|
||||
);
|
||||
|
||||
$response = apply_filters( "um_upload_image_process__{$key}", $response, $image_path, $src, $key, $user_id, $coord, $crop );
|
||||
|
||||
if ( ! in_array( $key, array( 'profile_photo', 'cover_photo' ) ) ) {
|
||||
if ( ! in_array( $key, array( 'profile_photo', 'cover_photo' ), true ) ) {
|
||||
$response = apply_filters( 'um_upload_stream_image_process', $response, $image_path, $src, $key, $user_id, $coord, $crop );
|
||||
}
|
||||
|
||||
|
||||
@@ -1607,7 +1607,7 @@ if ( ! class_exists( 'um\core\User' ) ) {
|
||||
|
||||
$expiry_time = UM()->options()->get( 'activation_link_expiry_time' );
|
||||
if ( ! empty( $expiry_time ) && is_numeric( $expiry_time ) ) {
|
||||
$this->profile['account_secret_hash_expiry'] = time() + $expiry_time;
|
||||
$this->profile['account_secret_hash_expiry'] = time() + $expiry_time * DAY_IN_SECONDS;
|
||||
$this->update_usermeta_info( 'account_secret_hash_expiry' );
|
||||
}
|
||||
|
||||
|
||||
+145
-3355
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -3,7 +3,7 @@
|
||||
* Plugin Name: Ultimate Member
|
||||
* Plugin URI: http://ultimatemember.com/
|
||||
* Description: The easiest way to create powerful online communities and beautiful user profiles with WordPress
|
||||
* Version: 2.8.3
|
||||
* Version: 2.8.4
|
||||
* Author: Ultimate Member
|
||||
* Author URI: http://ultimatemember.com/
|
||||
* Text Domain: ultimate-member
|
||||
|
||||
Reference in New Issue
Block a user