diff --git a/includes/admin/assets/js/um-admin-forms.js b/includes/admin/assets/js/um-admin-forms.js index 5101f89e..1bf28ef2 100644 --- a/includes/admin/assets/js/um-admin-forms.js +++ b/includes/admin/assets/js/um-admin-forms.js @@ -1,5 +1,53 @@ -jQuery(document).ready( function() { +function um_admin_init_users_select() { + if ( jQuery('.um-user-select-field').length ) { + var select2_atts = { + ajax: { + url: wp.ajax.settings.url, + dataType: 'json', + delay: 250, // delay in ms while typing when to perform a AJAX search + data: function( params ) { + return { + search: params.term, // search query + action: 'um_get_users', // AJAX action for admin-ajax.php + page: params.page || 1, // infinite scroll pagination + nonce: um_admin_scripts.nonce + }; + }, + processResults: function( response, params ) { + params.page = params.page || 1; + var options = []; + if ( response.data.users ) { + jQuery.each( response.data.users, function( index, text ) { + options.push( { id: text.ID, text: text.user_login + ' (#' + text.ID + ')' } ); + }); + } + + return { + results: options, + pagination: { + more: ( params.page * 20 ) < response.data.total_count + } + }; + }, + cache: true + }, + minimumInputLength: 0, // the minimum of symbols to input before perform a search + allowClear: true, + width: "100%", + allowHtml: true, + dropdownCssClass: 'um-select2-users-dropdown', + containerCssClass : 'um-select2-users-container', + placeholder: jQuery(this).data('placeholder') + }; + + jQuery('.um-user-select-field').select2( select2_atts ); + } +} + + +jQuery(document).ready( function() { + um_admin_init_users_select(); /** * Same page upgrade field diff --git a/includes/admin/core/class-admin-ajax-hooks.php b/includes/admin/core/class-admin-ajax-hooks.php index b3972317..23e27f06 100644 --- a/includes/admin/core/class-admin-ajax-hooks.php +++ b/includes/admin/core/class-admin-ajax-hooks.php @@ -30,7 +30,9 @@ if ( ! class_exists( 'um\admin\core\Admin_Ajax_Hooks' ) ) { add_action( 'wp_ajax_um_member_directory_default_filter_settings', array( UM()->member_directory(), 'default_filter_settings' ) ); add_action( 'wp_ajax_um_same_page_update', array( UM()->admin_settings(), 'same_page_update_ajax' ) ); + + add_action( 'wp_ajax_um_get_users', array( UM()->users(), 'get_users' ) ); } } -} \ No newline at end of file +} diff --git a/includes/admin/core/class-admin-enqueue.php b/includes/admin/core/class-admin-enqueue.php index f1be244f..56cf089c 100644 --- a/includes/admin/core/class-admin-enqueue.php +++ b/includes/admin/core/class-admin-enqueue.php @@ -231,11 +231,22 @@ if ( ! class_exists( 'um\admin\core\Admin_Enqueue' ) ) { * Load Forms */ function load_forms() { + if ( class_exists( 'WooCommerce' ) ) { + wp_dequeue_style( 'select2' ); + wp_deregister_style( 'select2' ); + + wp_dequeue_script( 'select2' ); + wp_deregister_script( 'select2' ); + } + + wp_register_script( 'select2', $this->front_js_baseurl . 'select2/select2.full' . $this->suffix . '.js', array( 'jquery', 'jquery-masonry' ), '4.0.13', true ); + wp_register_style( 'select2', $this->front_css_baseurl . 'select2/select2' . $this->suffix . '.css', array(), '4.0.13' ); + wp_register_style( 'um_ui', $this->front_css_baseurl . 'jquery-ui.css', array(), ultimatemember_version ); - wp_register_style( 'um_admin_forms', $this->css_url . 'um-admin-forms.css', array( 'wp-color-picker', 'um_ui' ), ultimatemember_version ); + wp_register_style( 'um_admin_forms', $this->css_url . 'um-admin-forms.css', array( 'wp-color-picker', 'um_ui', 'select2' ), ultimatemember_version ); wp_enqueue_style( 'um_admin_forms' ); - wp_register_script( 'um_admin_forms', $this->js_url . 'um-admin-forms.js', array( 'jquery', 'wp-i18n' ), ultimatemember_version, true ); + wp_register_script( 'um_admin_forms', $this->js_url . 'um-admin-forms.js', array( 'jquery', 'wp-i18n', 'select2' ), ultimatemember_version, true ); wp_localize_script( 'um_admin_forms', 'um_forms_data', array( 'successfully_redirect' => add_query_arg( array( 'page' => 'um_options', 'tab' => 'misc', 'msg' => 'updated' ), admin_url( 'admin.php' ) ), diff --git a/includes/admin/core/class-admin-forms.php b/includes/admin/core/class-admin-forms.php index fffb49a9..f1a52aad 100644 --- a/includes/admin/core/class-admin-forms.php +++ b/includes/admin/core/class-admin-forms.php @@ -548,6 +548,69 @@ if ( ! class_exists( 'um\admin\core\Admin_Forms' ) ) { } + /** + * @param $field_data + * + * @return bool|string + */ + function render_users_dropdown( $field_data ) { + if ( empty( $field_data['id'] ) ) { + return false; + } + + $multiple = ! empty( $field_data['multi'] ) ? 'multiple' : ''; + + $id = ( ! empty( $this->form_data['prefix_id'] ) ? $this->form_data['prefix_id'] : '' ) . '_' . $field_data['id']; + $id_attr = ' id="' . esc_attr( $id ) . '" '; + + $class = ! empty( $field_data['class'] ) ? $field_data['class'] . ' ' : ' '; + $class .= ! empty( $field_data['size'] ) ? 'um-' . $field_data['size'] . '-field' : 'um-long-field'; + $class_attr = ' class="um-forms-field um-user-select-field' . esc_attr( $class ) . '" '; + + $data = array( + 'field_id' => $field_data['id'], + ); + + $data_attr = ''; + foreach ( $data as $key => $value ) { + $data_attr .= ' data-' . $key . '="' . esc_attr( $value ) . '" '; + } + + $name = $field_data['id']; + $name = ! empty( $this->form_data['prefix_id'] ) ? $this->form_data['prefix_id'] . '[' . $name . ']' : $name; + $hidden_name_attr = ' name="' . $name . '" '; + $name = $name . ( ! empty( $field_data['multi'] ) ? '[]' : '' ); + $name_attr = ' name="' . $name . '" '; + + $value = $this->get_field_value( $field_data ); + + $users = array(); + if ( ! empty( $value ) ) { + $users = get_users( + array( + 'include' => $value, + 'fields' => array( 'ID', 'user_login' ), + ) + ); + } + + $options = ''; + if ( ! empty( $users ) ) { + foreach ( $users as $user ) { + $options .= ''; + } + } + + $hidden = ''; + if ( ! empty( $multiple ) ) { + $hidden = ""; + } + $html = "$hidden"; + + return $html; + } + + /** * @param $field_data * diff --git a/includes/admin/core/class-admin-notices.php b/includes/admin/core/class-admin-notices.php index 2489f966..7b6e98ee 100644 --- a/includes/admin/core/class-admin-notices.php +++ b/includes/admin/core/class-admin-notices.php @@ -48,6 +48,8 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) { $this->need_upgrade(); $this->check_wrong_licenses(); + $this->lock_registration(); + // removed for now to avoid the bad reviews //$this->reviews_notice(); @@ -214,6 +216,30 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) { } + /** + * Checking if the "Membership - Anyone can register" WordPress general setting is active + */ + public function lock_registration() { + $users_can_register = get_option( 'users_can_register' ); + if ( ! $users_can_register ) { + return; + } + + $allowed_html = array( + 'a' => array( + 'href' => array(), + ), + 'strong' => array(), + ); + + $this->add_notice( 'lock_registration', array( + 'class' => 'info', + 'message' => '
' . wp_kses( sprintf( __( 'The "Membership - Anyone can register" option on the general settings page is enabled. This means users can register via the standard WordPress wp-login.php page. If you do not want users to be able to register via this page and only register via the Ultimate Member registration form, you should deactivate this option. You can dismiss this notice if you wish to keep the wp-login.php registration page open.', 'ultimate-member' ), admin_url( 'options-general.php' ) . '#users_can_register' ), $allowed_html ) . '
', + 'dismissible' => true, + ), 10 ); + } + + /** * To store plugin languages */ diff --git a/includes/admin/core/class-admin-settings.php b/includes/admin/core/class-admin-settings.php index 95b39c28..bd8e0b67 100644 --- a/includes/admin/core/class-admin-settings.php +++ b/includes/admin/core/class-admin-settings.php @@ -3183,9 +3183,6 @@ Enable the Reset Password Limit: info_value( UM()->options() Reset Password Limit: options()->get('reset_password_limit_number') ?> Disable Reset Password Limit for Admins: info_value( UM()->options()->get('disable_admin_reset_password_limit'), 'yesno', true ) ?> -options()->get( 'wpadmin_allow_ips' ); if( ! empty( $wpadmin_allow_ips ) ) { ?> -Whitelisted Backend IPs: options()->get('wpadmin_allow_ips') ) ) )."\n"; ?> - options()->get('blocked_ips'); if( ! empty( $blocked_ips ) ){ ?> Blocked IP Addresses: options()->get('blocked_ips') ) )."\n"; ?> diff --git a/includes/admin/core/class-admin-users.php b/includes/admin/core/class-admin-users.php index c8f4c666..b7a0d2a1 100644 --- a/includes/admin/core/class-admin-users.php +++ b/includes/admin/core/class-admin-users.php @@ -42,6 +42,36 @@ if ( ! class_exists( 'um\admin\core\Admin_Users' ) ) { } + function get_users() { + UM()->admin()->check_ajax_nonce(); + + $search_request = ! empty( $_REQUEST['search'] ) ? sanitize_text_field( $_REQUEST['search'] ) : ''; + $page = ! empty( $_REQUEST['page'] ) ? absint( $_REQUEST['page'] ) : 1; + $per_page = 20; + + $args = array( + 'fields' => array( 'ID', 'user_login' ), + 'paged' => $page, + 'number' => $per_page + ); + + if ( ! empty( $search_request ) ) { + $args['search'] = $search_request; + } + + $users_query = new \WP_User_Query( $args ); + $users = $users_query->get_results(); + $total_count = $users_query->get_total(); + + wp_send_json_success( + array( + 'users' => $users, + 'total_count' => $total_count, + ) + ); + } + + /** * Restrict the edit/delete users via wp-admin screen by the UM role capabilities * diff --git a/includes/core/class-fields.php b/includes/core/class-fields.php index 57e528af..f6a1f7dd 100644 --- a/includes/core/class-fields.php +++ b/includes/core/class-fields.php @@ -4203,6 +4203,12 @@ if ( ! class_exists( 'um\core\Fields' ) ) { $res = stripslashes( $res ); } if ( 'description' === $data['metakey'] ) { + if ( UM()->options()->get( 'profile_show_html_bio' ) ) { + $res = make_clickable( wpautop( wp_kses_post( $res ) ) ); + } else { + $res = esc_html( $res ); + } + $res = nl2br( $res ); } diff --git a/includes/core/class-form.php b/includes/core/class-form.php index 4d4efb96..d70c989f 100644 --- a/includes/core/class-form.php +++ b/includes/core/class-form.php @@ -128,9 +128,9 @@ if ( ! class_exists( 'um\core\Form' ) ) { $values_array = $wpdb->get_col( $wpdb->prepare( - "SELECT DISTINCT meta_value - FROM $wpdb->usermeta - WHERE meta_key = %s AND + "SELECT DISTINCT meta_value + FROM $wpdb->usermeta + WHERE meta_key = %s AND meta_value != ''", $arr_options['post']['child_name'] ) @@ -662,7 +662,7 @@ if ( ! class_exists( 'um\core\Form' ) ) { case 'multiselect': case 'radio': case 'checkbox': - $form[ $k ] = array_map( 'sanitize_text_field', $form[ $k ] ); + $form[ $k ] = is_array( $form[ $k ] ) ? array_map( 'sanitize_text_field', $form[ $k ] ) : sanitize_text_field( $form[ $k ] ); break; } } diff --git a/includes/core/um-actions-login.php b/includes/core/um-actions-login.php index ce83d2b3..ec19ca6f 100644 --- a/includes/core/um-actions-login.php +++ b/includes/core/um-actions-login.php @@ -164,12 +164,15 @@ add_action( 'um_on_login_before_redirect', 'um_store_lastlogin_timestamp', 10, 1 */ function um_store_lastlogin_timestamp_( $login ) { $user = get_user_by( 'login', $login ); - um_store_lastlogin_timestamp( $user->ID ); - $attempts = (int) get_user_meta( $user->ID, 'password_rst_attempts', true ); - if ( $attempts ) { - //don't create meta but update if it's exists only - update_user_meta( $user->ID, 'password_rst_attempts', 0 ); + if ( false !== $user ) { + um_store_lastlogin_timestamp( $user->ID ); + + $attempts = (int) get_user_meta( $user->ID, 'password_rst_attempts', true ); + if ( $attempts ) { + //don't create meta but update if it's exists only + update_user_meta( $user->ID, 'password_rst_attempts', 0 ); + } } } add_action( 'wp_login', 'um_store_lastlogin_timestamp_' ); diff --git a/includes/core/um-filters-fields.php b/includes/core/um-filters-fields.php index 2589a3b3..b62854ab 100644 --- a/includes/core/um-filters-fields.php +++ b/includes/core/um-filters-fields.php @@ -211,7 +211,10 @@ function um_profile_field_filter_hook__textarea( $value, $data ) { $value = html_entity_decode( $value ); $value = preg_replace('$(https?://[a-z0-9_./?=-]+)(?![^<>]*>)$i', ' $1 ', $value." "); $value = preg_replace('$(www\.[a-z0-9_./?=-]+)(?![^<>]*>)$i', '$1 ', $value." "); - $value = wpautop($value); + + if ( ! ( isset( $data['metakey'] ) && 'description' === $data['metakey'] ) ) { + $value = wpautop( $value ); + } return $value; } @@ -421,8 +424,17 @@ function um_profile_field_filter_hook__( $value, $data, $type = '' ) { if ( strpos( $value, 'http://' ) !== 0 ) { $value = 'http://' . $value; } + + $value = str_replace('https://https://','https://',$value); + $value = str_replace('http://https://','https://',$value); + + $onclick_alert = ''; + if ( $value !== wp_validate_redirect( $value ) ) { + $onclick_alert = ' onclick="return confirm( \'' . sprintf( __( 'This link leads to a 3rd-party website. Make sure the link is safe and you really want to go to this website: `%s`', 'ultimate-member' ), $value ) . '\' );"'; + } + $data['url_target'] = ( isset( $data['url_target'] ) ) ? $data['url_target'] : '_blank'; - $value = ''.$alt.''; + $value = '' . esc_html( $alt ) . ''; } } diff --git a/includes/core/um-filters-login.php b/includes/core/um-filters-login.php index b5f5da37..185ad9b8 100644 --- a/includes/core/um-filters-login.php +++ b/includes/core/um-filters-login.php @@ -3,32 +3,6 @@ } -/** - * Filter to allow whitelisted IP to access the wp-admin login - * - * @param $allowed - * - * @return int - */ -function um_whitelisted_wpadmin_access( $allowed ) { - $ips = UM()->options()->get( 'wpadmin_allow_ips' ); - - if ( ! $ips ) { - return $allowed; - } - - $ips = array_map( 'rtrim', explode( "\n", $ips ) ); - $user_ip = um_user_ip(); - - if ( in_array( $user_ip, $ips, true ) ) { - $allowed = 1; - } - - return $allowed; -} -add_filter( 'um_whitelisted_wpadmin_access', 'um_whitelisted_wpadmin_access' ); - - /** * Filter to customize errors * diff --git a/readme.txt b/readme.txt index 0499a489..dc5ceef1 100644 --- a/readme.txt +++ b/readme.txt @@ -7,7 +7,7 @@ Tags: community, member, membership, user-profile, user-registration Requires PHP: 5.6 Requires at least: 5.0 Tested up to: 5.9 -Stable tag: 2.3.0 +Stable tag: 2.3.2 License: GNU Version 2 or Any Later Version License URI: http://www.gnu.org/licenses/gpl-3.0.txt @@ -133,6 +133,14 @@ Yes. Ultimate Member will work with any properly coded theme. However, some them The plugin works with popular caching plugins by automatically excluding Ultimate Member pages from being cached. This ensures other visitors to a page will not see the private information of another user. However, if you add features of Ultimate Member to other pages you have to exclude those pages from being cached through your cache plugin settings panel. += Does Ultimate Member restrict access to wp-login.php when the plugin is active? = + +The plugin does not restrict access to the wp-login.php page when active, so that our plugin does not interfere with the existing functionality of a website or other plugins that may utilise the default login page. If you wish to restrict access to the wp-login.php page you can use a plugin such as [WPS Hide Login](https://wordpress.org/plugins/wps-hide-login/) or another plugin that removes the ability to login via wp-login.php. + += Are Ultimate Member Login/Registration pages required? = + +No, you do not need to use our plugin’s login or registration pages and can use another plugin or the default WordPress methods for user registration and login. + == Screenshots == 1. Screenshot 1 @@ -155,6 +163,26 @@ The plugin works with popular caching plugins by automatically excluding Ultimat * To learn more about version 2.1 please see this [docs](https://docs.ultimatemember.com/article/1512-upgrade-2-1-0) * UM2.1+ is a significant update to the Member Directories' code base from 2.0.x. Please make sure you take a full-site backup with restore point before updating the plugin += 2.3.2: April 21, 2022 = + +* Enhancements: + + - Added: wp-admin notice with reminder about locking WordPress native registration for guests + - Added: Users dropdown field for Ultimate Member settings fields in wp-admin. It supports AJAX lazy loading + - Added: JS confirm when redirection from User Profile links to the 3rd-party URL + +* Bugfixes: + + - Fixed: PHP warning when there aren't proper user while login + - Fixed: Removing UM custom capabilities from global $wp_roles when uninstall + - Fixed: Removing UM custom roles from user roles after uninstall + - Fixed: Issue with echo XSS on User Profile + - Fixed: Sanitizing for the checkbox, radio, multiselect fields for PHP8 installations + +* Deprecated: + + - `um_whitelisted_wpadmin_access` hook and `wpadmin_allow_ips` option. They were unused and redundant since the 2.x version + = 2.3.1: February 9, 2022 = * Enhancements: diff --git a/ultimate-member.php b/ultimate-member.php index 2ac1bd34..8d77403c 100644 --- a/ultimate-member.php +++ b/ultimate-member.php @@ -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.3.1 +Version: 2.3.2 Author: Ultimate Member Author URI: http://ultimatemember.com/ Text Domain: ultimate-member diff --git a/uninstall.php b/uninstall.php index c322f44a..c1d6626a 100644 --- a/uninstall.php +++ b/uninstall.php @@ -68,12 +68,48 @@ if ( ! empty( $delete_options ) ) { wp_delete_post( $um_post->ID, 1 ); } + global $wp_roles; + + if ( class_exists( '\WP_Roles' ) ) { + if ( ! isset( $wp_roles ) ) { + $wp_roles = new \WP_Roles(); + } + + $role_keys = get_option( 'um_roles', array() ); + if ( $role_keys ) { + foreach ( $role_keys as $roleID ) { + $role_meta = get_option( "um_role_{$roleID}_meta" ); + if ( ! empty( $role_meta ) && ! empty( $wp_roles->roles[ $roleID ] ) ) { + $wp_roles->roles[ $roleID ] = array_diff( $wp_roles->roles[ $roleID ], $role_meta ); + } + } + } + + update_option( $wp_roles->role_key, $wp_roles->roles ); + } + //remove user role meta $role_keys = get_option( 'um_roles', array() ); if ( $role_keys ) { foreach ( $role_keys as $role_key ) { delete_option( 'um_role_' . $role_key . '_meta' ); } + + $um_custom_role_users = get_users( + array( + 'role__in' => $role_keys, + ) + ); + + if ( ! empty( $um_custom_role_users ) ) { + foreach ( $um_custom_role_users as $custom_role_user ) { + foreach ( $role_keys as $role_key ) { + if ( user_can( $custom_role_user, $role_key ) ) { + $custom_role_user->remove_role( $role_key ); + } + } + } + } } delete_option( '__ultimatemember_sitekey' );