mirror of
https://github.com/10h30/ultimatemember.git
synced 2026-07-17 05:33:36 +09:00
Merge pull request #1768 from ultimatemember/development/2.9.x
Version 2.11.1
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
"step": "installPlugin",
|
||||
"pluginZipFile": {
|
||||
"resource": "url",
|
||||
"url": "https:\/\/downloads.wordpress.org\/plugin\/ultimate-member.2.11.0.zip"
|
||||
"url": "https:\/\/downloads.wordpress.org\/plugin\/ultimate-member.2.11.1.zip"
|
||||
},
|
||||
"options": {
|
||||
"activate": true
|
||||
|
||||
@@ -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.11.0](https://github.com/ultimatemember/ultimatemember/releases/tag/2.11.0).
|
||||
[Official Release Version: 2.11.1](https://github.com/ultimatemember/ultimatemember/releases/tag/2.11.1).
|
||||
|
||||
## Changelog
|
||||
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
== Changelog ==
|
||||
|
||||
= 2.11.1 December 16, 2025 =
|
||||
|
||||
* Enhancements:
|
||||
|
||||
- Added: 'Privacy Options' for Member Directory. 'Who can see this member directory' and 'Allowed Roles'.
|
||||
- Added: 'Rate Limit' setting for nopriv AJAX actions.
|
||||
|
||||
* Bugfixes:
|
||||
|
||||
- Fixed: Security issue CVE ID: CVE-2025-13220. Used `shortcode_atts()` function to avoid using wrong attributes.
|
||||
- Fixed: Security issue CVE ID: CVE-2025-13217. Implementing proper input sanitization and escaping for iframe URLs in YouTube, Vimeo, and Google Maps embeds.
|
||||
- Fixed: Security issue CVE ID: CVE-2025-14081. Filtering fields based on user permissions during Account form submission.
|
||||
- Fixed: Security issue CVE ID: CVE-2025-12492. Added directory privacy settings and added rate limiting.
|
||||
|
||||
* Templates required update:
|
||||
|
||||
- members.php
|
||||
- members-grid.php
|
||||
- members-list.php
|
||||
|
||||
= 2.11.0 December 02, 2025 =
|
||||
|
||||
* Enhancements:
|
||||
|
||||
@@ -284,6 +284,12 @@ if ( ! class_exists( 'um\admin\Admin' ) ) {
|
||||
'_um_mode' => array(
|
||||
'sanitize' => 'key',
|
||||
),
|
||||
'_um_privacy' => array(
|
||||
'sanitize' => 'absint',
|
||||
),
|
||||
'_um_privacy_roles' => array(
|
||||
'sanitize' => array( $this, 'sanitize_existed_role' ),
|
||||
),
|
||||
'_um_view_types' => array(
|
||||
'sanitize' => array( $this, 'sanitize_md_view_types' ),
|
||||
),
|
||||
|
||||
@@ -209,6 +209,13 @@ if ( ! class_exists( 'um\admin\Secure' ) ) {
|
||||
$scanner_content .= '</span>';
|
||||
|
||||
$secure_fields = array(
|
||||
array(
|
||||
'id' => 'ajax_nopriv_rate_limit',
|
||||
'type' => 'checkbox',
|
||||
'label' => __( 'Rate Limit', 'ultimate-member' ),
|
||||
'checkbox_label' => __( 'Enable Rate Limiting', 'ultimate-member' ),
|
||||
'description' => __( 'This prevents brute-force enumeration attempts in guest AJAX requests.', 'ultimate-member' ),
|
||||
),
|
||||
array(
|
||||
'id' => 'banned_capabilities',
|
||||
'type' => 'multi_checkbox',
|
||||
|
||||
@@ -1493,6 +1493,10 @@ class Site_Health {
|
||||
'label' => __( 'Advanced > Security', 'ultimate-member' ),
|
||||
'value' => '---------------------------------------------------------------------',
|
||||
),
|
||||
'ajax_nopriv_rate_limit' => array(
|
||||
'label' => __( 'Enable Rate Limiting', 'ultimate-member' ),
|
||||
'value' => UM()->options()->get( 'ajax_nopriv_rate_limit' ) ? $labels['yes'] : $labels['no'],
|
||||
),
|
||||
'banned_capabilities' => array(
|
||||
'label' => __( 'Banned Administrative Capabilities', 'ultimate-member' ),
|
||||
'value' => ! empty( $banned_capabilities ) ? implode( ', ', $banned_capabilities ) : '',
|
||||
@@ -2570,6 +2574,32 @@ class Site_Health {
|
||||
*/
|
||||
$debug_info = apply_filters( 'um_debug_member_directory_general_extend', $debug_info, $directory_id );
|
||||
|
||||
$md_privacy_options = array(
|
||||
0 => __( 'Anyone', 'ultimate-member' ),
|
||||
1 => __( 'Guests only', 'ultimate-member' ),
|
||||
2 => __( 'Members only', 'ultimate-member' ),
|
||||
3 => __( 'Only specific roles', 'ultimate-member' ),
|
||||
);
|
||||
$directory_privacy = get_post_meta( $directory_id, '_um_privacy', true );
|
||||
|
||||
$debug_info[] = array(
|
||||
'privacy' => array(
|
||||
'label' => __( 'Who can see this member directory', 'ultimate-member' ),
|
||||
'value' => array_key_exists( $directory_privacy, $md_privacy_options ) ? $md_privacy_options[ $directory_privacy ] : __( 'Invalid', 'ultimate-member' ),
|
||||
),
|
||||
);
|
||||
if ( 3 === absint( $directory_privacy ) ) {
|
||||
$directory_privacy_roles = get_post_meta( $directory_id, '_um_privacy_roles', true );
|
||||
$directory_privacy_roles = ! empty( $directory_privacy_roles ) && is_array( $directory_privacy_roles ) ? $directory_privacy_roles : array();
|
||||
|
||||
$debug_info[] = array(
|
||||
'privacy_roles' => array(
|
||||
'label' => __( 'Allowed roles', 'ultimate-member' ),
|
||||
'value' => $directory_privacy_roles,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if ( isset( $options[ get_post_meta( $directory_id, '_um_sortby', true ) ] ) ) {
|
||||
$sortby_label = $options[ get_post_meta( $directory_id, '_um_sortby', true ) ];
|
||||
} else {
|
||||
|
||||
@@ -728,7 +728,7 @@ if ( ! class_exists( 'um\admin\core\Admin_Metabox' ) ) {
|
||||
* @param $object
|
||||
* @param $box
|
||||
*/
|
||||
function load_metabox_directory( $object, $box ) {
|
||||
public function load_metabox_directory( $object, $box ) {
|
||||
$box['id'] = str_replace( 'um-admin-form-', '', $box['id'] );
|
||||
|
||||
preg_match('#\{.*?\}#s', $box['id'], $matches );
|
||||
@@ -857,6 +857,7 @@ if ( ! class_exists( 'um\admin\core\Admin_Metabox' ) ) {
|
||||
public function add_metabox_directory() {
|
||||
add_meta_box( 'submitdiv', __( 'Publish', 'ultimate-member' ), array( &$this, 'custom_submitdiv' ), 'um_directory', 'side', 'high' );
|
||||
add_meta_box( 'um-admin-form-general', __( 'General Options', 'ultimate-member' ), array( &$this, 'load_metabox_directory' ), 'um_directory', 'normal', 'default' );
|
||||
add_meta_box( 'um-admin-form-privacy', __( 'Privacy Options', 'ultimate-member' ), array( &$this, 'load_metabox_directory' ), 'um_directory', 'normal', 'default' );
|
||||
add_meta_box( 'um-admin-form-sorting', __( 'Sorting', 'ultimate-member' ), array( &$this, 'load_metabox_directory' ), 'um_directory', 'normal', 'default' );
|
||||
add_meta_box( 'um-admin-form-profile', __( 'Profile Card', 'ultimate-member' ), array( &$this, 'load_metabox_directory' ), 'um_directory', 'normal', 'default' );
|
||||
add_meta_box( 'um-admin-form-search', __( 'Search Options', 'ultimate-member' ), array( &$this, 'load_metabox_directory' ), 'um_directory', 'normal', 'default' );
|
||||
|
||||
@@ -1032,6 +1032,9 @@ if ( ! class_exists( 'um\admin\core\Admin_Settings' ) ) {
|
||||
'display_login_form_notice' => array(
|
||||
'sanitize' => 'bool',
|
||||
),
|
||||
'ajax_nopriv_rate_limit' => array(
|
||||
'sanitize' => 'bool',
|
||||
),
|
||||
'banned_capabilities' => array(
|
||||
'sanitize' => array( UM()->admin(), 'sanitize_wp_capabilities_assoc' ),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$fields = array(
|
||||
array(
|
||||
'id' => '_um_privacy',
|
||||
'type' => 'select',
|
||||
'label' => __( 'Who can see this member directory', 'ultimate-member' ),
|
||||
'description' => __( 'Select which users can view this member directory. Minimum recommended visibility is `Members only`. Please pay attention that visible for guests (anyone) member directory can have sensitive information.', 'ultimate-member' ),
|
||||
'options' => array(
|
||||
0 => __( 'Anyone', 'ultimate-member' ),
|
||||
1 => __( 'Guests only', 'ultimate-member' ),
|
||||
2 => __( 'Members only', 'ultimate-member' ),
|
||||
3 => __( 'Only specific roles', 'ultimate-member' ),
|
||||
),
|
||||
'value' => UM()->query()->get_meta_value( '_um_privacy', null, 2 ),
|
||||
),
|
||||
array(
|
||||
'id' => '_um_privacy_roles',
|
||||
'type' => 'select',
|
||||
'multi' => true,
|
||||
'label' => __( 'Allowed roles', 'ultimate-member' ),
|
||||
'description' => __( 'Select the the user roles allowed to view this member directory.', 'ultimate-member' ),
|
||||
'options' => UM()->roles()->get_roles(),
|
||||
'placeholder' => __( 'Choose user roles...', 'ultimate-member' ),
|
||||
'conditional' => array( '_um_privacy', '=', '3' ),
|
||||
'value' => UM()->query()->get_meta_value( '_um_privacy_roles', null, 'na' ),
|
||||
),
|
||||
);
|
||||
|
||||
$fields = apply_filters( 'um_admin_extend_directory_options_privacy', $fields );
|
||||
?>
|
||||
|
||||
<div class="um-admin-metabox">
|
||||
<?php
|
||||
UM()->admin_forms(
|
||||
array(
|
||||
'class' => 'um-member-directory-privacy um-half-column',
|
||||
'prefix_id' => 'um_metadata',
|
||||
'fields' => $fields,
|
||||
)
|
||||
)->render_form();
|
||||
?>
|
||||
|
||||
<div class="clear"></div>
|
||||
</div>
|
||||
@@ -141,6 +141,8 @@ if ( ! class_exists( 'um\Config' ) ) {
|
||||
'_um_core' => 'members',
|
||||
'_um_template' => 'members',
|
||||
'_um_mode' => 'directory',
|
||||
'_um_privacy' => 2,
|
||||
'_um_privacy_roles' => array(),
|
||||
'_um_view_types' => array( 'grid' ),
|
||||
'_um_default_view' => 'grid',
|
||||
'_um_roles' => array(),
|
||||
@@ -729,6 +731,7 @@ if ( ! class_exists( 'um\Config' ) ) {
|
||||
'lock_register_forms' => false,
|
||||
'display_login_form_notice' => false,
|
||||
'secure_ban_admins_accounts' => false,
|
||||
'ajax_nopriv_rate_limit' => true,
|
||||
'banned_capabilities' => array( 'manage_options', 'promote_users', 'level_10' ),
|
||||
'secure_notify_admins_banned_accounts' => false,
|
||||
'secure_notify_admins_banned_accounts__interval' => 'instant',
|
||||
|
||||
@@ -47,6 +47,37 @@ if ( ! class_exists( 'UM_Functions' ) ) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user rate limit has been reached based on the provided context
|
||||
*
|
||||
* @param string $context The context for which the rate limit check is being performed
|
||||
* @param int|null $requests_num Number requests per minute. Default 10.
|
||||
*
|
||||
* @return bool True if the rate limit has been reached, false otherwise
|
||||
*/
|
||||
public function is_rate_limited( $context, $requests_num = 10 ) {
|
||||
if ( is_user_logged_in() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! UM()->options()->get( 'ajax_nopriv_rate_limit' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$transient_name = 'um_ajax_nopriv_rate_limit_' . md5( $context . '_' . $_SERVER['REMOTE_ADDR'] );
|
||||
$rate_limit = get_transient( $transient_name );
|
||||
$requests_num = apply_filters( 'um_ajax_rate_limit_requests_num', $requests_num, $context ); // 10 requests per minute per IP
|
||||
|
||||
if ( false === $rate_limit ) {
|
||||
set_transient( $transient_name, 1, MINUTE_IN_SECONDS );
|
||||
} elseif ( $rate_limit < $requests_num ) {
|
||||
set_transient( $transient_name, $rate_limit + 1, MINUTE_IN_SECONDS );
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* What type of request is this?
|
||||
|
||||
@@ -1375,7 +1375,6 @@ if ( ! class_exists( 'UM' ) ) {
|
||||
require_once 'core/um-actions-form.php';
|
||||
require_once 'core/um-actions-access.php';
|
||||
require_once 'core/um-actions-wpadmin.php';
|
||||
require_once 'core/um-actions-ajax.php';
|
||||
require_once 'core/um-actions-login.php';
|
||||
require_once 'core/um-actions-register.php';
|
||||
require_once 'core/um-actions-profile.php';
|
||||
|
||||
@@ -473,23 +473,27 @@ if ( ! class_exists( 'um\core\Account' ) ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param $fields
|
||||
* @param $shortcode_args
|
||||
* @return mixed
|
||||
*/
|
||||
function filter_fields_by_attrs( $fields, $shortcode_args ) {
|
||||
private function filter_fields_by_attrs( $fields, $shortcode_args ) {
|
||||
foreach ( $fields as $k => $field ) {
|
||||
if ( isset( $shortcode_args[ $field['metakey'] ] ) && 0 == $shortcode_args[ $field['metakey'] ] ) {
|
||||
unset( $fields[ $k ] );
|
||||
continue;
|
||||
}
|
||||
|
||||
// required user permission 'required_perm' - it's field attribute predefined in the field data in code.
|
||||
if ( isset( $data['required_perm'] ) && ! UM()->roles()->um_user_can( $data['required_perm'] ) ) {
|
||||
unset( $fields[ $k ] );
|
||||
}
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Init displayed fields for security check
|
||||
*
|
||||
|
||||
@@ -266,6 +266,10 @@ if ( ! class_exists( 'um\core\Files' ) ) {
|
||||
public function ajax_remove_file() {
|
||||
UM()->check_ajax_nonce();
|
||||
|
||||
if ( UM()->is_rate_limited( 'remove_file' ) ) {
|
||||
wp_send_json_error( __( 'Too many requests', 'ultimate-member' ) );
|
||||
}
|
||||
|
||||
if ( empty( $_POST['src'] ) ) {
|
||||
wp_send_json_error( __( 'Wrong path', 'ultimate-member' ) );
|
||||
}
|
||||
@@ -314,6 +318,11 @@ if ( ! class_exists( 'um\core\Files' ) ) {
|
||||
*/
|
||||
public function ajax_resize_image() {
|
||||
UM()->check_ajax_nonce();
|
||||
|
||||
if ( UM()->is_rate_limited( 'resize_image' ) ) {
|
||||
wp_send_json_error( __( 'Too many requests', 'ultimate-member' ) );
|
||||
}
|
||||
|
||||
// phpcs:disable WordPress.Security.NonceVerification -- verified by the `check_ajax_nonce()`
|
||||
if ( ! isset( $_REQUEST['src'], $_REQUEST['coord'], $_REQUEST['key'] ) ) {
|
||||
wp_send_json_error( esc_js( __( 'Invalid parameters', 'ultimate-member' ) ) );
|
||||
@@ -447,6 +456,10 @@ if ( ! class_exists( 'um\core\Files' ) ) {
|
||||
* @throws Exception
|
||||
*/
|
||||
public function ajax_image_upload() {
|
||||
if ( UM()->is_rate_limited( 'upload_image' ) ) {
|
||||
wp_send_json_error( __( 'Too many requests', 'ultimate-member' ) );
|
||||
}
|
||||
|
||||
$ret['error'] = null;
|
||||
$ret = array();
|
||||
|
||||
@@ -645,6 +658,10 @@ if ( ! class_exists( 'um\core\Files' ) ) {
|
||||
* @throws Exception
|
||||
*/
|
||||
public function ajax_file_upload() {
|
||||
if ( UM()->is_rate_limited( 'upload_file' ) ) {
|
||||
wp_send_json_error( __( 'Too many requests', 'ultimate-member' ) );
|
||||
}
|
||||
|
||||
$ret['error'] = null;
|
||||
$ret = array();
|
||||
|
||||
|
||||
@@ -116,6 +116,10 @@ if ( ! class_exists( 'um\core\Form' ) ) {
|
||||
public function ajax_select_options() {
|
||||
UM()->check_ajax_nonce();
|
||||
|
||||
if ( UM()->is_rate_limited( 'select_options' ) ) {
|
||||
wp_send_json_error( __( 'Too many requests', 'ultimate-member' ) );
|
||||
}
|
||||
|
||||
// phpcs:disable WordPress.Security.NonceVerification
|
||||
|
||||
$arr_options = array();
|
||||
|
||||
@@ -595,6 +595,10 @@ if ( ! class_exists( 'um\core\Member_Directory_Meta' ) ) {
|
||||
public function ajax_get_members() {
|
||||
UM()->check_ajax_nonce();
|
||||
|
||||
if ( UM()->is_rate_limited( 'member_directory' ) ) {
|
||||
wp_send_json_error( __( 'Too many requests', 'ultimate-member' ) );
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$blog_id = get_current_blog_id();
|
||||
@@ -609,6 +613,10 @@ if ( ! class_exists( 'um\core\Member_Directory_Meta' ) ) {
|
||||
}
|
||||
// phpcs:enable WordPress.Security.NonceVerification -- verified via `UM()->check_ajax_nonce();`.
|
||||
|
||||
if ( ! $this->can_view_directory( $directory_id ) ) {
|
||||
wp_send_json_error( __( 'You cannot see this member directory', 'ultimate-member' ) );
|
||||
}
|
||||
|
||||
$directory_data = UM()->query()->post_data( $directory_id );
|
||||
|
||||
// Predefined result for user without capabilities to see other members.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
namespace um\core;
|
||||
|
||||
use Exception;
|
||||
use WP_Post;
|
||||
use WP_User_Query;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
@@ -123,11 +124,79 @@ if ( ! class_exists( 'um\core\Member_Directory' ) ) {
|
||||
public function __construct() {
|
||||
add_filter( 'init', array( &$this, 'init_variables' ) );
|
||||
|
||||
add_action( 'wp_insert_post', array( &$this, 'set_token' ), 10, 3 );
|
||||
add_action( 'template_redirect', array( &$this, 'access_members' ), 555 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set member directory token as soon as it's created.
|
||||
*
|
||||
* @param int $post_ID
|
||||
* @param WP_Post $post
|
||||
* @param bool $update
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function set_token( $post_ID, $post, $update ) {
|
||||
if ( 'um_directory' === $post->post_type && ! $update ) {
|
||||
$this->set_directory_hash( $post_ID );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the user can view the member directory.
|
||||
*
|
||||
* @param int $directory_id
|
||||
* @param int|null $user_id
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_view_directory( $directory_id, $user_id = null ) {
|
||||
if ( is_null( $user_id ) && is_user_logged_in() ) {
|
||||
$user_id = get_current_user_id();
|
||||
}
|
||||
|
||||
$can_view = false;
|
||||
$privacy = get_post_meta( $directory_id, '_um_privacy', true );
|
||||
if ( '' === $privacy ) {
|
||||
$can_view = true;
|
||||
} else {
|
||||
$privacy = absint( $privacy );
|
||||
|
||||
switch ( $privacy ) {
|
||||
case 0:
|
||||
$can_view = true;
|
||||
break;
|
||||
case 1:
|
||||
if ( ! is_user_logged_in() ) {
|
||||
$can_view = true;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if ( is_user_logged_in() ) {
|
||||
$can_view = true;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if ( is_user_logged_in() ) {
|
||||
$privacy_roles = get_post_meta( $directory_id, '_um_privacy_roles', true );
|
||||
$privacy_roles = ! empty( $privacy_roles ) && is_array( $privacy_roles ) ? $privacy_roles : array();
|
||||
|
||||
$current_user_roles = um_user( 'roles' );
|
||||
if ( ! empty( $current_user_roles ) && count( array_intersect( $current_user_roles, $privacy_roles ) ) > 0 ) {
|
||||
$can_view = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return apply_filters( 'um_directory_user_can_view', $can_view, $directory_id, $user_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the WordPress core searching fields in wp_users query.
|
||||
*
|
||||
* @since 2.6.10
|
||||
* @version 2.10.2
|
||||
* @param array|null $qv WP_User_Query variables.
|
||||
@@ -202,29 +271,104 @@ if ( ! class_exists( 'um\core\Member_Directory' ) ) {
|
||||
*
|
||||
* @return bool|int
|
||||
*/
|
||||
function get_directory_by_hash( $hash ) {
|
||||
public function get_directory_by_hash( $hash ) {
|
||||
global $wpdb;
|
||||
|
||||
$directory_id = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE SUBSTRING( MD5( ID ), 11, 5 ) = %s", $hash ) );
|
||||
|
||||
$directory_id = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT post_id
|
||||
FROM {$wpdb->postmeta}
|
||||
WHERE meta_key = '_um_directory_token' AND
|
||||
meta_value = %s
|
||||
LIMIT 1",
|
||||
$hash
|
||||
)
|
||||
);
|
||||
if ( empty( $directory_id ) ) {
|
||||
return false;
|
||||
// Fallback, use old value.
|
||||
$directory_id = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT ID
|
||||
FROM {$wpdb->posts}
|
||||
WHERE SUBSTRING( MD5( ID ), 11, 5 ) = %s",
|
||||
$hash
|
||||
)
|
||||
);
|
||||
if ( empty( $directory_id ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return (int) $directory_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a secure random token for each directory
|
||||
*
|
||||
* @param int $id
|
||||
*
|
||||
* @return false|string
|
||||
*/
|
||||
public function set_directory_hash( $id ) {
|
||||
$unique_hash = wp_generate_password( 5, false );
|
||||
$result = update_post_meta( $id, '_um_directory_token', $unique_hash );
|
||||
if ( false === $result ) {
|
||||
return false;
|
||||
}
|
||||
return $unique_hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $id
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
function get_directory_hash( $id ) {
|
||||
$hash = substr( md5( $id ), 10, 5 );
|
||||
public function get_directory_hash( $id ) {
|
||||
$hash = get_post_meta( $id, '_um_directory_token', true );
|
||||
if ( '' === $hash ) {
|
||||
// Set the hash if empty.
|
||||
$hash = $this->set_directory_hash( $id );
|
||||
}
|
||||
if ( empty( $hash ) ) {
|
||||
// Fallback, use old value.
|
||||
$hash = substr( md5( $id ), 10, 5 );
|
||||
}
|
||||
return $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a secure random token for each user card
|
||||
*
|
||||
* @param int $id
|
||||
*
|
||||
* @return false|string
|
||||
*/
|
||||
public function set_user_hash( $id ) {
|
||||
$unique_hash = wp_generate_password( 5, false );
|
||||
$result = update_user_meta( $id, '_um_card_anchor_token', $unique_hash );
|
||||
if ( false === $result ) {
|
||||
return false;
|
||||
}
|
||||
return $unique_hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $id
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
public function get_user_hash( $id ) {
|
||||
$hash = get_user_meta( $id, '_um_card_anchor_token', true );
|
||||
if ( '' === $hash ) {
|
||||
// Set the hash if empty.
|
||||
$hash = $this->set_user_hash( $id );
|
||||
}
|
||||
if ( empty( $hash ) ) {
|
||||
// Fallback, use old value.
|
||||
$hash = substr( md5( $id ), 10, 5 );
|
||||
}
|
||||
return $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get view Type template
|
||||
@@ -673,7 +817,7 @@ if ( ! class_exists( 'um\core\Member_Directory' ) ) {
|
||||
*/
|
||||
$attrs = apply_filters( 'um_search_fields', $attrs, $field_key, $directory_data['form_id'] );
|
||||
|
||||
$unique_hash = substr( md5( $directory_data['form_id'] ), 10, 5 );
|
||||
$unique_hash = $this->get_directory_hash( $directory_data['form_id'] );
|
||||
|
||||
ob_start();
|
||||
|
||||
@@ -2545,16 +2689,15 @@ if ( ! class_exists( 'um\core\Member_Directory' ) ) {
|
||||
$hook_after_user_name = ob_get_clean();
|
||||
|
||||
$data_array = array(
|
||||
'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()->common()->users()->get_status( $user_id ) ),
|
||||
'account_status_name' => esc_html( UM()->common()->users()->get_status( $user_id, 'formatted' ) ),
|
||||
'card_anchor' => esc_html( $this->get_user_hash( $user_id ) ),
|
||||
'role' => is_user_logged_in() ? esc_html( um_user( 'role' ) ) : 'undefined', // make the role hidden for the nopriv requests.
|
||||
'account_status' => is_user_logged_in() ? esc_html( UM()->common()->users()->get_status( $user_id ) ) : 'undefined', // make the status hidden for the nopriv requests.
|
||||
'account_status_name' => is_user_logged_in() ? esc_html( UM()->common()->users()->get_status( $user_id, 'formatted' ) ) : __( 'Undefined', 'ultimate-member' ), // make the status hidden for the nopriv requests.
|
||||
'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() ),
|
||||
'edit_profile_url' => $can_edit ? 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,
|
||||
@@ -2788,13 +2931,16 @@ if ( ! class_exists( 'um\core\Member_Directory' ) ) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Main Query function for getting members via AJAX
|
||||
*/
|
||||
function ajax_get_members() {
|
||||
public function ajax_get_members() {
|
||||
UM()->check_ajax_nonce();
|
||||
|
||||
if ( UM()->is_rate_limited( 'member_directory' ) ) {
|
||||
wp_send_json_error( __( 'Too many requests', 'ultimate-member' ) );
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
if ( empty( $_POST['directory_id'] ) ) {
|
||||
@@ -2807,6 +2953,10 @@ if ( ! class_exists( 'um\core\Member_Directory' ) ) {
|
||||
wp_send_json_error( __( 'Wrong member directory data', 'ultimate-member' ) );
|
||||
}
|
||||
|
||||
if ( ! $this->can_view_directory( $directory_id ) ) {
|
||||
wp_send_json_error( __( 'You cannot see this member directory', 'ultimate-member' ) );
|
||||
}
|
||||
|
||||
$directory_data = UM()->query()->post_data( $directory_id );
|
||||
|
||||
//predefined result for user without capabilities to see other members
|
||||
|
||||
@@ -527,10 +527,10 @@ if ( ! class_exists( 'um\core\Shortcodes' ) ) {
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function ultimatemember_login( $args = array() ) {
|
||||
public function ultimatemember_login( $args = array() ) {
|
||||
global $wpdb;
|
||||
|
||||
$args = ! empty( $args ) ? $args : array();
|
||||
$args = shortcode_atts( array(), $args, 'ultimatemember_login' );
|
||||
|
||||
$default_login = $wpdb->get_var(
|
||||
"SELECT pm.post_id
|
||||
@@ -547,23 +547,18 @@ if ( ! class_exists( 'um\core\Shortcodes' ) ) {
|
||||
$shortcode_attrs .= " {$key}=\"{$value}\"";
|
||||
}
|
||||
|
||||
if ( version_compare( get_bloginfo('version'),'5.4', '<' ) ) {
|
||||
return do_shortcode( "[ultimatemember {$shortcode_attrs} /]" );
|
||||
} else {
|
||||
return apply_shortcodes( "[ultimatemember {$shortcode_attrs} /]" );
|
||||
}
|
||||
return apply_shortcodes( "[ultimatemember {$shortcode_attrs} /]" );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function ultimatemember_register( $args = array() ) {
|
||||
public function ultimatemember_register( $args = array() ) {
|
||||
global $wpdb;
|
||||
|
||||
$args = ! empty( $args ) ? $args : array();
|
||||
$args = shortcode_atts( array(), $args, 'ultimatemember_register' );
|
||||
|
||||
$default_register = $wpdb->get_var(
|
||||
"SELECT pm.post_id
|
||||
@@ -580,23 +575,18 @@ if ( ! class_exists( 'um\core\Shortcodes' ) ) {
|
||||
$shortcode_attrs .= " {$key}=\"{$value}\"";
|
||||
}
|
||||
|
||||
if ( version_compare( get_bloginfo('version'),'5.4', '<' ) ) {
|
||||
return do_shortcode( "[ultimatemember {$shortcode_attrs} /]" );
|
||||
} else {
|
||||
return apply_shortcodes( "[ultimatemember {$shortcode_attrs} /]" );
|
||||
}
|
||||
return apply_shortcodes( "[ultimatemember {$shortcode_attrs} /]" );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function ultimatemember_profile( $args = array() ) {
|
||||
public function ultimatemember_profile( $args = array() ) {
|
||||
global $wpdb;
|
||||
|
||||
$args = ! empty( $args ) ? $args : array();
|
||||
$args = shortcode_atts( array(), $args, 'ultimatemember_profile' );
|
||||
|
||||
$default_profile = $wpdb->get_var(
|
||||
"SELECT pm.post_id
|
||||
@@ -614,23 +604,18 @@ if ( ! class_exists( 'um\core\Shortcodes' ) ) {
|
||||
$shortcode_attrs .= " {$key}=\"{$value}\"";
|
||||
}
|
||||
|
||||
if ( version_compare( get_bloginfo('version'),'5.4', '<' ) ) {
|
||||
return do_shortcode( "[ultimatemember {$shortcode_attrs} /]" );
|
||||
} else {
|
||||
return apply_shortcodes( "[ultimatemember {$shortcode_attrs} /]" );
|
||||
}
|
||||
return apply_shortcodes( "[ultimatemember {$shortcode_attrs} /]" );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param array $args
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function ultimatemember_directory( $args = array() ) {
|
||||
public function ultimatemember_directory( $args = array() ) {
|
||||
global $wpdb;
|
||||
|
||||
$args = ! empty( $args ) ? $args : array();
|
||||
$args = shortcode_atts( array(), $args, 'ultimatemember_directory' );
|
||||
|
||||
$default_directory = $wpdb->get_var(
|
||||
"SELECT pm.post_id
|
||||
@@ -648,11 +633,7 @@ if ( ! class_exists( 'um\core\Shortcodes' ) ) {
|
||||
$shortcode_attrs .= " {$key}=\"{$value}\"";
|
||||
}
|
||||
|
||||
if ( version_compare( get_bloginfo('version'),'5.4', '<' ) ) {
|
||||
return do_shortcode( "[ultimatemember {$shortcode_attrs} /]" );
|
||||
} else {
|
||||
return apply_shortcodes( "[ultimatemember {$shortcode_attrs} /]" );
|
||||
}
|
||||
return apply_shortcodes( "[ultimatemember {$shortcode_attrs} /]" );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -794,6 +775,9 @@ if ( ! class_exists( 'um\core\Shortcodes' ) ) {
|
||||
}
|
||||
|
||||
if ( 'directory' === $args['mode'] ) {
|
||||
if ( ! UM()->member_directory()->can_view_directory( $this->form_id ) ) {
|
||||
return ''; // Checking for privacy settings of the member directory
|
||||
}
|
||||
wp_enqueue_script( 'um_members' );
|
||||
wp_enqueue_style( 'um_members' );
|
||||
}
|
||||
|
||||
@@ -105,6 +105,10 @@ if ( ! class_exists( 'um\core\User_posts' ) ) {
|
||||
function load_posts() {
|
||||
UM()->check_ajax_nonce();
|
||||
|
||||
if ( UM()->is_rate_limited( 'paginate_posts' ) ) {
|
||||
wp_send_json_error( __( 'Too many requests', 'ultimate-member' ) );
|
||||
}
|
||||
|
||||
$author = ! empty( $_POST['author'] ) ? absint( $_POST['author'] ) : get_current_user_id();
|
||||
$page = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 0;
|
||||
|
||||
@@ -153,6 +157,10 @@ if ( ! class_exists( 'um\core\User_posts' ) ) {
|
||||
function load_comments() {
|
||||
UM()->check_ajax_nonce();
|
||||
|
||||
if ( UM()->is_rate_limited( 'paginate_comments' ) ) {
|
||||
wp_send_json_error( __( 'Too many requests', 'ultimate-member' ) );
|
||||
}
|
||||
|
||||
$user_id = ! empty( $_POST['user_id'] ) ? absint( $_POST['user_id'] ) : get_current_user_id();
|
||||
$page = ! empty( $_POST['page'] ) ? absint( $_POST['page'] ) : 0;
|
||||
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
<?php
|
||||
// Exit if accessed directly
|
||||
if ( ! defined( 'ABSPATH' ) ) exit;
|
||||
|
||||
/**
|
||||
* Run check if username exists
|
||||
* @uses action hooks: wp_ajax_nopriv_ultimatemember_check_username_exists, wp_ajax_ultimatemember_check_username_exists
|
||||
* @return boolean
|
||||
*/
|
||||
function ultimatemember_check_username_exists() {
|
||||
UM()->check_ajax_nonce();
|
||||
|
||||
$username = isset( $_REQUEST['username'] ) ? sanitize_user( $_REQUEST['username'] ) : '';
|
||||
$exists = username_exists( $username );
|
||||
|
||||
/**
|
||||
* UM hook
|
||||
*
|
||||
* @type filter
|
||||
* @title um_validate_username_exists
|
||||
* @description Change username exists validation
|
||||
* @input_vars
|
||||
* [{"var":"$exists","type":"bool","desc":"Exists?"},
|
||||
* {"var":"$username","type":"string","desc":"Username"}]
|
||||
* @change_log
|
||||
* ["Since: 2.0"]
|
||||
* @usage
|
||||
* <?php add_filter( 'um_validate_username_exists', 'function_name', 10, 2 ); ?>
|
||||
* @example
|
||||
* <?php
|
||||
* add_filter( 'um_validate_username_exists', 'my_validate_username_exists', 10, 2 );
|
||||
* function my_account_pre_updating_profile( $exists, $username ) {
|
||||
* // your code here
|
||||
* return $exists;
|
||||
* }
|
||||
* ?>
|
||||
*/
|
||||
$exists = apply_filters( 'um_validate_username_exists', $exists, $username );
|
||||
|
||||
if ( $exists ) {
|
||||
echo 1;
|
||||
} else {
|
||||
echo 0;
|
||||
}
|
||||
|
||||
die();
|
||||
}
|
||||
add_action('wp_ajax_nopriv_ultimatemember_check_username_exists', 'ultimatemember_check_username_exists');
|
||||
add_action('wp_ajax_ultimatemember_check_username_exists', 'ultimatemember_check_username_exists');
|
||||
@@ -76,15 +76,15 @@ function um_profile_field_filter_hook__youtube_video( $value, $data ) {
|
||||
}
|
||||
$value = ( strstr( $value, 'http' ) || strstr( $value, '://' ) ) ? um_youtube_id_from_url( $value ) : $value;
|
||||
if ( false !== $value ) {
|
||||
$value = 'https://www.youtube.com/embed/' . $value;
|
||||
$value = '<div class="um-youtube">'
|
||||
. '<iframe width="600" height="450" src="https://www.youtube.com/embed/' . $value . '" frameborder="0" allowfullscreen></iframe>'
|
||||
. '<iframe width="600" height="450" src="' . esc_url( $value ) . '" frameborder="0" allowfullscreen></iframe>'
|
||||
. '</div>';
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
add_filter( 'um_profile_field_filter_hook__youtube_video', 'um_profile_field_filter_hook__youtube_video', 99, 2 );
|
||||
|
||||
|
||||
/**
|
||||
* Outputs a spotify iframe
|
||||
*
|
||||
@@ -127,8 +127,9 @@ function um_profile_field_filter_hook__vimeo_video( $value, $data ) {
|
||||
}
|
||||
|
||||
$value = ! is_numeric( $value ) ? (int) substr( parse_url( $value, PHP_URL_PATH ), 1 ) : $value;
|
||||
$value = 'https://player.vimeo.com/video/' . $value;
|
||||
$value = '<div class="um-vimeo">
|
||||
<iframe src="https://player.vimeo.com/video/' . $value . '" width="600" height="450" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
|
||||
<iframe src="' . esc_url( $value ) . '" width="600" height="450" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
|
||||
</div>';
|
||||
return $value;
|
||||
}
|
||||
@@ -199,14 +200,14 @@ function um_profile_field_filter_hook__googlemap( $value, $data ) {
|
||||
if ( ! $value ) {
|
||||
return '';
|
||||
}
|
||||
$value = 'https://maps.google.it/maps?q=' . urlencode( $value ) . '&output=embed';
|
||||
$value = '<div class="um-googlemap">
|
||||
<iframe width="600" height="450" frameborder="0" style="border:0" src="https://maps.google.it/maps?q=' . urlencode( $value ) . '&output=embed"></iframe>
|
||||
<iframe width="600" height="450" frameborder="0" style="border:0" src="' . esc_url( $value ) . '"></iframe>
|
||||
</div>';
|
||||
return $value;
|
||||
}
|
||||
add_filter( 'um_profile_field_filter_hook__googlemap', 'um_profile_field_filter_hook__googlemap', 99, 2 );
|
||||
|
||||
|
||||
/**
|
||||
* User's registration date
|
||||
*
|
||||
|
||||
+23
-143
@@ -167,6 +167,26 @@ No specific extensions are needed. But we highly recommended keep active these P
|
||||
|
||||
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
|
||||
|
||||
= 2.11.1 2025-12-16 =
|
||||
|
||||
**Enhancements**
|
||||
|
||||
* Added: 'Privacy Options' for Member Directory. 'Who can see this member directory' and 'Allowed Roles'.
|
||||
* Added: 'Rate Limit' setting for nopriv AJAX actions.
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
* Fixed: Security issue CVE ID: CVE-2025-13220. Used `shortcode_atts()` function to avoid using wrong attributes.
|
||||
* Fixed: Security issue CVE ID: CVE-2025-13217. Implementing proper input sanitization and escaping for iframe URLs in YouTube, Vimeo, and Google Maps embeds.
|
||||
* Fixed: Security issue CVE ID: CVE-2025-14081. Filtering fields based on user permissions during Account form submission.
|
||||
* Fixed: Security issue CVE ID: CVE-2025-12492. Added directory privacy settings and added rate limiting.
|
||||
|
||||
* Templates required update:
|
||||
|
||||
* members.php
|
||||
* members-grid.php
|
||||
* members-list.php
|
||||
|
||||
= 2.11.0 2025-12-02 =
|
||||
|
||||
**Enhancements**
|
||||
@@ -185,153 +205,13 @@ IMPORTANT: PLEASE UPDATE THE PLUGIN TO AT LEAST VERSION 2.6.7 IMMEDIATELY. VERSI
|
||||
* Fixed: PHP warnings for roles without meta data.
|
||||
* Fixed: Typo in labels.
|
||||
|
||||
= 2.10.6 2025-10-02 =
|
||||
|
||||
**Enhancements**
|
||||
|
||||
* Added: Avoid caching of the UM Forms on the mobile devices via adding the nocache headers to the screens with UM Forms.
|
||||
* Added: Filter hook `um_get_empty_status_users_query_result` for changing default query on the different websites to optimize it.
|
||||
* Added: Filter hook `um_admin_settings_get_pages_list_args` for changing WP_Query arguments for getting pages visible in the dropdown fields in UM Settings.
|
||||
* Added: JS filter hook `um_admin_blocks_prefixes_excluded` for excluding 3rd-party Gutenberg blocks with predefined prefixes from UM restriction arguments.
|
||||
* Added: WebP file-extension support for UM uploader.
|
||||
* Added: `UM_LICENSE_REQUEST_DEBUG` constant for debugging license activation process when it's needed.
|
||||
* Added: `Extensions_Updater` class to standardize the upgrade process in UM extensions.
|
||||
* Added: Sanitize handlers `sanitize_array_key_int` and `sanitize_array_key` for making sanitize in UM extensions' settings.
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
* Fixed: Changed the view and the edit user profile links in the comments section on the frontend.
|
||||
* Fixed: `Contains` conditional logic operand when value is array.
|
||||
* Fixed: Getting cover_size for displaying it in the member directory card.
|
||||
* Fixed: Filter's range for numeric-type fields to avoid getting the empty values.
|
||||
* Fixed: Integer validation for the 'start_of_week' WP native setting.
|
||||
* Fixed: Dependencies with Action Scheduler library.
|
||||
|
||||
**Cached and optimized/minified assets(JS/CSS) must be flushed/re-generated after upgrade**
|
||||
|
||||
= 2.10.5 2025-06-25 =
|
||||
|
||||
**Enhancements**
|
||||
|
||||
* Added: Filter hook [`um_password_reset_form_primary_btn_classes`](https://ultimatemember.github.io/ultimatemember/hooks/um_password_reset_form_primary_btn_classes.html) for primary button classes in UM Password Reset form.
|
||||
* Added: Filter hook [`um_login_form_primary_btn_classes`](https://ultimatemember.github.io/ultimatemember/hooks/um_login_form_primary_btn_classes.html) for primary button classes in UM Login form.
|
||||
* Added: Filter hook [`um_register_form_primary_btn_classes`](https://ultimatemember.github.io/ultimatemember/hooks/um_register_form_primary_btn_classes.html) for primary button classes in UM Registration form.
|
||||
* Tweak: Refactored Site Health data, added hooks for 3rd-party integration.
|
||||
* Tweak: Avoid using `um_user( 'password_reset_link' )` and make it directly with `UM()->password()->reset_url( $user_id )` for getting a proper reset URL.
|
||||
* Tweak: Avoid using `um_user( 'account_activation_link' )` and make it directly with `UM()->permalinks()->activate_url( $user_id )` for getting a proper activation URL.
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
* Fixed: Stripped shortcodes in the user data during the Account, Registration and Profile forms submission. (Thanks to [MissVeronica](https://github.com/MissVeronica))
|
||||
* Fixed: Email placeholders values.
|
||||
* Fixed: Refactor deactivation logic to un-schedule Action Scheduler actions.
|
||||
* Fixed: Action Scheduler library errors. Updated to the recent 3.9.2 version.
|
||||
* Fixed: Secondary email field validation.
|
||||
* Fixed: Action Scheduler batch actions with users who have Undefined status.
|
||||
* Fixed: Restrictions for 3rd-party Gutenberg Blocks.
|
||||
* Fixed: Date/time picker filter-types range query on Member Directories.
|
||||
* Fixed: Renamed "Macedonia, the former Yugoslav Republic of" to the official "North Macedonia".
|
||||
|
||||
**Deprecated**
|
||||
|
||||
* Fully deprecated `account_activation_link_tags_patterns( $placeholders )` function. It's not used previously. Used email function arguments instead.
|
||||
* Fully deprecated `account_activation_link_tags_replaces( $replace_placeholders )` function. It's not used previously. Used email function arguments instead.
|
||||
* Fully deprecated `UM()->profile()->add_placeholder()` function. Used email function arguments instead.
|
||||
* Fully deprecated `UM()->profile()->add_replace_placeholder()` function. Used email function arguments instead.
|
||||
* Fully deprecated `UM()->user()->add_activation_placeholder()` function. Used email function arguments instead.
|
||||
* Fully deprecated `UM()->user()->add_activation_replace_placeholder()` function. Used email function arguments instead.
|
||||
* Deprecated `UM()->user()->maybe_generate_password_reset_key( $userdata )` function. Use `UM()->common()->users()->maybe_generate_password_reset_key( $userdata )` instead.
|
||||
* Deprecated `UM()->user()->set_last_login()` function. Use `UM()->common()->users()->set_last_login( $user_id )` instead.
|
||||
|
||||
**Templates required update**
|
||||
|
||||
* password-reset.php
|
||||
|
||||
**Cached and optimized/minified assets(JS/CSS) must be flushed/re-generated after upgrade**
|
||||
|
||||
= 2.10.4 2025-05-15 =
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
* Fixed: Security issue CVE ID: CVE-2025-47691. Used "sniccowp/php-scoper-wordpress-excludes" for getting the recent WordPress functions list and added them to the dynamic blacklist based on the WordPress version.
|
||||
* Fixed: The Action Scheduler action `um_set_default_account_status`. Case when some users were approved manually or deleted, and we need to reset the admin notice. Added `error_log()` to the wrong conditions.
|
||||
* Fixed: Reset Password request from not a predefined password reset page. It's possible to submit reset password form sitewide using block or shortcode.
|
||||
* Fixed: Setting 'Allow users to change email' for the Account page. It works now for any role instead of only the roles with 'Can edit other member accounts?' capability enabled.
|
||||
|
||||
= 2.10.3 2025-04-24 =
|
||||
|
||||
**Enhancements**
|
||||
|
||||
* Added: The `Ignore the "User Role > Registration Options"` setting. It provides an ability to auto-approve users if they were created via wp-admin > Users screen.
|
||||
* Tweak: Avoid email notifications to Administrator about user registration via wp-admin > Users screen.
|
||||
* Tweak: Updated the Action Scheduler implementation to improve flexibility and clarity. Refactor Action Scheduler for not only email handling.
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
* Fixed: Member Directory styles when it's rendered on the Gutenberg builder page.
|
||||
* Fixed: Member Directory filtering query when the custom users metatable is used.
|
||||
* Fixed: PHP Warning that occurs when using the `getimagesize` function with an image from an external source.
|
||||
* Fixed: Reset Password email notification's the {password_reset_link}` placeholder.
|
||||
* Fixed: Changed "Turkey" to the current official term "Türkiye".
|
||||
|
||||
**Cached and optimized/minified assets(JS/CSS) must be flushed/re-generated after upgrade**
|
||||
|
||||
= 2.10.2 2025-04-02 =
|
||||
|
||||
**Enhancements**
|
||||
|
||||
* Added: `UM()->common()-filesystem()::maybe_init_wp_filesystem();` method.
|
||||
* Added: `UM()->common()-filesystem()::remove_dir();` method.
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
* Fixed: Security issue CVE ID: CVE-2025-1702. Reviewed general search scripts and suggested another solution that uses only `$wpdb->prepare()`.
|
||||
|
||||
= 2.10.1 2025-03-03 =
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
* Fixed: Security issue CVE ID: CVE-2025-1702.
|
||||
* Fixed: Activation link redirects to Reset Password after registration without password field and required email activation.
|
||||
* Fixed: Honeypot scripts/styles for themes without pre-rendered shortcodes. Enqueue honeypot scripts/styles everytime.
|
||||
* Fixed: Profile photo metadata when Gravatar image is used.
|
||||
|
||||
**Cached and optimized/minified assets(JS/CSS) must be flushed/re-generated after upgrade**
|
||||
|
||||
= 2.10.0 2025-02-18 =
|
||||
|
||||
**Enhancements**
|
||||
|
||||
* Added: User Profile `form-id` attribute and updated code for Profile/Cover photos actions dropdowns.
|
||||
* Added: Honeypot scripts/styles via `wp_add_inline_script()`, `wp_add_inline_style()` changed from direct adding in header and footer.
|
||||
* Updated: We've made improvements to requests for extension updates to boost stability.
|
||||
* Updated: PHP requirement - the minimum PHP version is now upgraded to 7.0.
|
||||
* Updated: Using $wpdb and WPCS for queries. Set minimum required version to 6.2 due to using %i for `$wpdb->prepare()`.
|
||||
* Updated: Revised wp-admin user actions handling. Now, the required capability is `edit_users` instead of `manage_options`.
|
||||
* Removed: User Profile hidden inputs on view mode.
|
||||
* Tweak: WPCS enhancements.
|
||||
|
||||
**Bugfixes**
|
||||
|
||||
* Fixed: Security issue CVE ID: CVE-2024-12276.
|
||||
* Fixed: Custom usermeta table metakeys for filtering in member directory (from `_money_spent` to `wc_money_spent_` and added `wc_order_count_`).
|
||||
* Fixed: Layout for "Download your data" and "Erase of your data" fields.
|
||||
* Fixed: Image sizes used for Open Graph meta in User Profile headers are now corrected.
|
||||
* Fixed: "Delete account text" settings visibility issue in wp-admin.
|
||||
* Fixed: The "Privacy Policy" field in the registration form. Disallowed HTML from the "Privacy Policy" content (like `<form>`) is filtered out by the `wp_kses()` function.
|
||||
* Fixed: Password fields are now sanitized the WordPress native way, with `wp_unslash()` omitted post-submission.
|
||||
|
||||
**Templates required update**
|
||||
|
||||
* gdpr-register.php
|
||||
* profile.php
|
||||
|
||||
**Cached and optimized/minified assets(JS/CSS) must be flushed/re-generated after upgrade**
|
||||
|
||||
[See changelog for all versions](https://plugins.svn.wordpress.org/ultimate-member/trunk/changelog.txt).
|
||||
|
||||
== Upgrade Notice ==
|
||||
|
||||
= 2.11.1 =
|
||||
This version fixes a security related bug. Upgrade immediately.
|
||||
|
||||
= 2.10.4 =
|
||||
This version fixes a security related bug. Upgrade immediately.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*
|
||||
* Page: "Members"
|
||||
*
|
||||
* @version 2.6.1
|
||||
* @version 2.11.1
|
||||
*
|
||||
* @var array $args
|
||||
* @var bool $cover_photos
|
||||
@@ -23,8 +23,8 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$unique_hash = substr( md5( $args['form_id'] ), 10, 5 ); ?>
|
||||
|
||||
$unique_hash = UM()->member_directory()->get_directory_hash( $args['form_id'] );
|
||||
?>
|
||||
<script type="text/template" id="tmpl-um-member-grid-<?php echo esc_attr( $unique_hash ) ?>">
|
||||
<div class="um-members um-members-grid">
|
||||
<div class="um-gutter-sizer"></div>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*
|
||||
* Page: "Members"
|
||||
*
|
||||
* @version 2.6.1
|
||||
* @version 2.11.1
|
||||
*
|
||||
* @var array $args
|
||||
* @var bool $cover_photos
|
||||
@@ -19,10 +19,12 @@
|
||||
* @var array $reveal_fields
|
||||
* @var string $no_users
|
||||
*/
|
||||
if ( ! defined( 'ABSPATH' ) ) exit;
|
||||
|
||||
$unique_hash = substr( md5( $args['form_id'] ), 10, 5 ); ?>
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$unique_hash = UM()->member_directory()->get_directory_hash( $args['form_id'] );
|
||||
?>
|
||||
<script type="text/template" id="tmpl-um-member-list-<?php echo esc_attr( $unique_hash ) ?>">
|
||||
<div class="um-members um-members-list">
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*
|
||||
* Page: "Members"
|
||||
*
|
||||
* @version 2.6.8
|
||||
* @version 2.11.1
|
||||
*
|
||||
* @var array $args
|
||||
*/
|
||||
@@ -23,7 +23,7 @@ foreach ( UM()->config()->core_directory_meta['members'] as $k => $v ) {
|
||||
$def_args[ $key ] = $v;
|
||||
}
|
||||
|
||||
$unique_hash = substr( md5( $args['form_id'] ), 10, 5 );
|
||||
$unique_hash = UM()->member_directory()->get_directory_hash( $args['form_id'] );
|
||||
|
||||
$args = array_merge( $def_args, $args );
|
||||
|
||||
@@ -237,8 +237,8 @@ if ( ( ( $search && $show_search ) || ( $filters && $show_filters && count( $sea
|
||||
$postid = ! empty( $post->ID ) ? $post->ID : '';
|
||||
?>
|
||||
|
||||
<div class="um <?php echo esc_attr( $this->get_class( $mode ) ); ?> um-<?php echo esc_attr( substr( md5( $form_id ), 10, 5 ) ); ?>"
|
||||
data-hash="<?php echo esc_attr( substr( md5( $form_id ), 10, 5 ) ) ?>" data-base-post="<?php echo esc_attr( $postid ) ?>"
|
||||
<div class="um <?php echo esc_attr( $this->get_class( $mode ) ); ?> um-<?php echo esc_attr( $unique_hash ); ?>"
|
||||
data-hash="<?php echo esc_attr( $unique_hash ) ?>" data-base-post="<?php echo esc_attr( $postid ) ?>"
|
||||
data-must-search="<?php echo esc_attr( $must_search ); ?>" data-searched="<?php echo $not_searched ? '0' : '1'; ?>"
|
||||
data-view_type="<?php echo esc_attr( $current_view ) ?>" data-page="<?php echo esc_attr( $current_page ) ?>"
|
||||
data-sorting="<?php echo esc_attr( $sort_from_url ) ?>">
|
||||
@@ -313,7 +313,7 @@ $postid = ! empty( $post->ID ) ? $post->ID : '';
|
||||
<?php $items = array();
|
||||
|
||||
foreach ( $sorting_options as $value => $title ) {
|
||||
$items[] = '<a href="javascript:void(0);" data-directory-hash="' . esc_attr( substr( md5( $form_id ), 10, 5 ) ) . '" class="um-sortyng-by-' . esc_attr( $value ) . '" data-value="' . esc_attr( $value ) . '" data-selected="' . ( ( $sort_from_url == $value ) ? '1' : '0' ) . '" data-default="' . ( ( $default_sorting == $value ) ? '1' : '0' ) . '">' . $title . '</a>'; ?>
|
||||
$items[] = '<a href="javascript:void(0);" data-directory-hash="' . esc_attr( $unique_hash ) . '" class="um-sortyng-by-' . esc_attr( $value ) . '" data-value="' . esc_attr( $value ) . '" data-selected="' . ( ( $sort_from_url == $value ) ? '1' : '0' ) . '" data-default="' . ( ( $default_sorting == $value ) ? '1' : '0' ) . '">' . $title . '</a>'; ?>
|
||||
<?php }
|
||||
|
||||
UM()->member_directory()->dropdown_menu( '.um-member-directory-sorting-a', 'click', $items ); ?>
|
||||
|
||||
+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.11.0
|
||||
* Version: 2.11.1
|
||||
* Author: Ultimate Member
|
||||
* Author URI: http://ultimatemember.com/
|
||||
* License: GPLv3
|
||||
|
||||
Reference in New Issue
Block a user