From e529b9d50f3b8c36701f2f1a2dc81b0a48162c44 Mon Sep 17 00:00:00 2001 From: yuriinalivaiko Date: Wed, 2 Apr 2025 22:23:14 +0300 Subject: [PATCH 01/26] - fixed PHP Warning that occurs when using the `getimagesize` function with an image from an external source. --- includes/core/um-actions-profile.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/includes/core/um-actions-profile.php b/includes/core/um-actions-profile.php index 101f5a21..520c42c2 100644 --- a/includes/core/um-actions-profile.php +++ b/includes/core/um-actions-profile.php @@ -737,18 +737,18 @@ function um_profile_dynamic_meta_desc() { $image_info = array(); $image_width = $image_size; $image_height = $image_size; - if ( false === strpos( $image, 'gravatar.com' ) ) { - // Ignore Gravatar image here and handler a real image. + $upload_dir = wp_get_upload_dir(); + if ( false !== strpos( $image, $upload_dir['url'] ) ) { + // Get the image size of the real image. $image = current( explode( '?', $image ) ); // strip $_GET attributes from photo URL. $image_path = wp_normalize_path( ABSPATH . wp_parse_url( $image, PHP_URL_PATH ) ); - $image_info = wp_check_filetype( $image_path ); - $imagesizes = getimagesize( $image_path ); - if ( is_array( $imagesizes ) ) { - list( $image_width, $image_height ) = $imagesizes; + if ( file_exists( $image_path ) ) { + $image_info = wp_check_filetype( $image_path ); + $imagesizes = @getimagesize( $image_path ); + if ( is_array( $imagesizes ) ) { + list( $image_width, $image_height ) = $imagesizes; + } } - } else { - // Gravatar image. - $image_path = esc_url_raw( $image ); } $person = array( From f013118830c3505a5e9f04f2658d1dafa1c2b389 Mon Sep 17 00:00:00 2001 From: Mykyta Synelnikov Date: Thu, 3 Apr 2025 12:28:28 +0300 Subject: [PATCH 02/26] Update Ultimate Member plugin to version 2.10.3 Bump version references across the plugin files from 2.10.2 to 2.10.3. This includes updates to documentation, changelog placeholders, and blueprint download URLs to ensure consistency and preparation for the new release. --- .wordpress-org/blueprints/blueprint.json | 2 +- README.md | 2 +- readme.txt | 4 ++++ ultimate-member.php | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.wordpress-org/blueprints/blueprint.json b/.wordpress-org/blueprints/blueprint.json index 0d2c64e4..75651f33 100644 --- a/.wordpress-org/blueprints/blueprint.json +++ b/.wordpress-org/blueprints/blueprint.json @@ -15,7 +15,7 @@ "step": "installPlugin", "pluginZipFile": { "resource": "url", - "url": "https:\/\/downloads.wordpress.org\/plugin\/ultimate-member.2.10.2.zip" + "url": "https:\/\/downloads.wordpress.org\/plugin\/ultimate-member.2.10.3.zip" }, "options": { "activate": true diff --git a/README.md b/README.md index 9412b326..45927cc6 100644 --- a/README.md +++ b/README.md @@ -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.10.2](https://github.com/ultimatemember/ultimatemember/releases/tag/2.10.2). +[Official Release Version: 2.10.3](https://github.com/ultimatemember/ultimatemember/releases/tag/2.10.3). ## Changelog diff --git a/readme.txt b/readme.txt index ab90339a..688735d0 100644 --- a/readme.txt +++ b/readme.txt @@ -167,6 +167,10 @@ 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.10.3 2025-04-xx = + + + = 2.10.2 2025-04-02 = **Enhancements** diff --git a/ultimate-member.php b/ultimate-member.php index 960538ca..3b702a32 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.10.2 + * Version: 2.10.3-alpha * Author: Ultimate Member * Author URI: http://ultimatemember.com/ * Text Domain: ultimate-member From ccc7cd099bbd92988bc47a27e108b4cd5280f7eb Mon Sep 17 00:00:00 2001 From: Mykyta Synelnikov Date: Thu, 3 Apr 2025 13:05:10 +0300 Subject: [PATCH 03/26] Add comments and legacy key for WooCommerce 9.1.0 support Added comments to clarify the usage of meta keys introduced in WooCommerce 9.1.0 and marked TODOs for future cleanup. Also included a legacy key, `_money_spent`, for backward compatibility with WooCommerce versions below 9.1.0. --- includes/admin/core/class-admin-settings.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/includes/admin/core/class-admin-settings.php b/includes/admin/core/class-admin-settings.php index deb4f727..ab2c8668 100644 --- a/includes/admin/core/class-admin-settings.php +++ b/includes/admin/core/class-admin-settings.php @@ -100,19 +100,20 @@ if ( ! class_exists( 'um\admin\core\Admin_Settings' ) ) { $sites = get_sites( array( 'fields' => 'ids' ) ); foreach ( $sites as $blog_id ) { $metakeys[] = $wpdb->get_blog_prefix( $blog_id ) . 'capabilities'; - $metakeys[] = 'wc_money_spent_' . rtrim( $wpdb->get_blog_prefix( $blog_id ), '_' ); - $metakeys[] = 'wc_order_count_' . rtrim( $wpdb->get_blog_prefix( $blog_id ), '_' ); + $metakeys[] = 'wc_money_spent_' . rtrim( $wpdb->get_blog_prefix( $blog_id ), '_' ); // Is used since Woocommerce 9.1.0 + $metakeys[] = 'wc_order_count_' . rtrim( $wpdb->get_blog_prefix( $blog_id ), '_' ); // Is used since Woocommerce 9.1.0 TODO remove as soon as used 'um_wc_order_count_' } } else { $blog_id = get_current_blog_id(); $metakeys[] = $wpdb->get_blog_prefix( $blog_id ) . 'capabilities'; - $metakeys[] = 'wc_money_spent_' . rtrim( $wpdb->get_blog_prefix( $blog_id ), '_' ); - $metakeys[] = 'wc_order_count_' . rtrim( $wpdb->get_blog_prefix( $blog_id ), '_' ); + $metakeys[] = 'wc_money_spent_' . rtrim( $wpdb->get_blog_prefix( $blog_id ), '_' ); // Is used since Woocommerce 9.1.0 + $metakeys[] = 'wc_order_count_' . rtrim( $wpdb->get_blog_prefix( $blog_id ), '_' ); // Is used since Woocommerce 9.1.0 TODO remove as soon as used 'um_wc_order_count_' } - //member directory data + // Member directory data $metakeys[] = 'um_member_directory_data'; $metakeys[] = '_um_verified'; + $metakeys[] = '_money_spent'; // Legacy since Woocommerce 9.1.0. TODO remove as soon as stop support Woo below 9.1.0 version $metakeys[] = '_completed'; $metakeys[] = '_reviews_avg'; From 3858c6af8300a5123f63453d23ad5fb259dcf55d Mon Sep 17 00:00:00 2001 From: Mykyta Synelnikov Date: Fri, 4 Apr 2025 12:59:10 +0300 Subject: [PATCH 04/26] Refactor Action Scheduler for not only email handling. Updated the Action Scheduler implementation to improve flexibility and clarity. Replaced the 'enable_action_scheduler' option with 'enable_as_email_sending' for better specificity. Introduced hook-based checks to selectively enable email scheduling, ensuring compatibility and optimized performance. --- includes/action-scheduler/class-init.php | 27 ++++++++++++-------- includes/admin/core/class-admin-settings.php | 2 +- includes/class-config.php | 2 +- includes/common/actions/class-emails.php | 12 +++++++-- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/includes/action-scheduler/class-init.php b/includes/action-scheduler/class-init.php index 16b96bc3..0c2356fe 100644 --- a/includes/action-scheduler/class-init.php +++ b/includes/action-scheduler/class-init.php @@ -45,11 +45,10 @@ if ( ! class_exists( 'um\action_scheduler\Init' ) ) { add_action( 'init', array( $this, 'add_notice' ) ); } else { add_filter( 'um_settings_structure', array( $this, 'add_setting' ) ); - - if ( UM()->options()->get( 'enable_action_scheduler' ) ) { - $this->enabled = true; - $this->load_library( true ); - } + // Since 2.10.3 we load library as soon as there are required library files. + // Options above are used for enable/disable some Action Scheduler hooks e.g. um_dispatch_email. + $this->enabled = true; + $this->load_library( true ); } } @@ -77,11 +76,11 @@ if ( ! class_exists( 'um\action_scheduler\Init' ) ) { */ public function add_setting( $settings ) { $settings['advanced']['sections']['features']['form_sections']['features']['fields'][] = array( - 'id' => 'enable_action_scheduler', + 'id' => 'enable_as_email_sending', 'type' => 'checkbox', - 'label' => __( 'Action Scheduler', 'ultimate-member' ), - 'checkbox_label' => __( 'Enable Action Scheduler', 'ultimate-member' ), - 'description' => __( 'Check this box if you want to use the Ultimate Member action scheduler. By enabling it, certain tasks like sending system emails will be scheduled to run at optimal times, which can help reduce the load on your server', 'ultimate-member' ), + 'label' => __( 'Email sending by Action Scheduler', 'ultimate-member' ), + 'checkbox_label' => __( 'Enable Action Scheduler for Ultimate Member emails sending', 'ultimate-member' ), + 'description' => __( 'Check this box if you want to use the Action Scheduler for Ultimate Member emails sending. By enabling it, sending system emails will be scheduled to run at optimal times, which can help reduce the load on your server', 'ultimate-member' ), ); return $settings; @@ -131,6 +130,10 @@ if ( ! class_exists( 'um\action_scheduler\Init' ) ) { return $this->verify_wc_action_scheduler() || $this->load_library(); } + public function is_hook_enabled( $hook ) { + return apply_filters( 'um_action_scheduler_is_hook_enabled', $this->is_enabled(), $hook ); + } + /** * Enqueue an action to run one time, as soon as possible. * If Action Scheduler is disabled then do_action_ref_array is called to run action right away. @@ -144,11 +147,12 @@ if ( ! class_exists( 'um\action_scheduler\Init' ) ) { * @return int Еhe action’s ID. Zero if there was an error scheduling the action. The error will be sent to error_log */ public function enqueue_async_action( $hook, $args = array(), $group = '', $unique = false, $priority = 10 ) { - if ( $this->enabled ) { + if ( $this->is_hook_enabled( $hook ) ) { $group = $this->set_group( $group ); return as_enqueue_async_action( $hook, $args, $group, $unique, $priority ); } + // Make then standard action without Action Scheduler. do_action_ref_array( $hook, $args ); return 0; } @@ -166,11 +170,12 @@ if ( ! class_exists( 'um\action_scheduler\Init' ) ) { * @return int The action’s ID. Zero if there was an error scheduling the action. The error will be sent to error_log. */ public function schedule_single_action( $timestamp, $hook, $args = array(), $group = '', $unique = false, $priority = 10 ) { - if ( $this->enabled ) { + if ( $this->is_hook_enabled( $hook ) ) { $group = $this->set_group( $group ); return as_schedule_single_action( $timestamp, $hook, $args, $group, $unique, $priority ); } + // Make then standard action without Action Scheduler. do_action_ref_array( $hook, $args ); return 0; } diff --git a/includes/admin/core/class-admin-settings.php b/includes/admin/core/class-admin-settings.php index ab2c8668..7c51275d 100644 --- a/includes/admin/core/class-admin-settings.php +++ b/includes/admin/core/class-admin-settings.php @@ -1011,7 +1011,7 @@ if ( ! class_exists( 'um\admin\core\Admin_Settings' ) ) { 'enable_blocks' => array( 'sanitize' => 'bool', ), - 'enable_action_scheduler' => array( + 'enable_as_email_sending' => array( 'sanitize' => 'bool', ), 'rest_api_version' => array( diff --git a/includes/class-config.php b/includes/class-config.php index 0a3e9fbb..d7fa2aea 100644 --- a/includes/class-config.php +++ b/includes/class-config.php @@ -733,7 +733,7 @@ if ( ! class_exists( 'um\Config' ) ) { 'secure_notify_admins_banned_accounts__interval' => 'instant', 'secure_allowed_redirect_hosts' => '', 'delete_comments' => false, - 'enable_action_scheduler' => false, + 'enable_as_email_sending' => UM()->options()->get( 'enable_action_scheduler' ), // Use legacy option value by default. It helps during update to set the same value. The last version when we used 'enable_action_scheduler' is 2.10.2 ); add_filter( 'um_get_tabs_from_config', '__return_true' ); diff --git a/includes/common/actions/class-emails.php b/includes/common/actions/class-emails.php index 57991450..f4c485be 100644 --- a/includes/common/actions/class-emails.php +++ b/includes/common/actions/class-emails.php @@ -17,10 +17,18 @@ if ( ! class_exists( 'um\common\actions\Emails' ) ) { class Emails { public function __construct() { + add_filter( 'um_action_scheduler_is_hook_enabled', array( $this, 'is_enabled' ), 10, 2 ); add_action( 'um_dispatch_email', array( $this, 'send' ), 10, 3 ); add_action( 'um_before_email_notification_sending', array( $this, 'before_email_send' ), 10, 2 ); } + public function is_enabled( $is_enabled, $hook ) { + if ( 'um_dispatch_email' === $hook ) { + $is_enabled = UM()->options()->get( 'enable_as_email_sending' ); + } + return $is_enabled; + } + /** * Send an email * @@ -36,7 +44,7 @@ if ( ! class_exists( 'um\common\actions\Emails' ) ) { // @todo Workaround for now. Maybe we need to put base $user_id everytime if ( array_key_exists( 'fetch_user_id', $args ) ) { // When Action Scheduler is enabled, email sending script is located out of basic functionality, so we need to fetch the user for replace placeholders. - if ( UM()->maybe_action_scheduler()->is_enabled() ) { + if ( UM()->maybe_action_scheduler()->is_hook_enabled( 'um_dispatch_email' ) ) { um_fetch_user( $args['fetch_user_id'] ); } unset( $args['fetch_user_id'] ); @@ -56,7 +64,7 @@ if ( ! class_exists( 'um\common\actions\Emails' ) ) { * @return void */ public function before_email_send( $email, $template ) { - if ( ! UM()->maybe_action_scheduler()->is_enabled() ) { + if ( ! UM()->maybe_action_scheduler()->is_hook_enabled( 'um_dispatch_email' ) ) { return; } From 08a7b48d53afa8908d688f051efa753d48ce527c Mon Sep 17 00:00:00 2001 From: ashubawork Date: Mon, 7 Apr 2025 15:01:07 +0300 Subject: [PATCH 05/26] - add schedule action for account_status check --- includes/common/class-users.php | 63 +++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/includes/common/class-users.php b/includes/common/class-users.php index e6fcaa5e..a2c2ad83 100644 --- a/includes/common/class-users.php +++ b/includes/common/class-users.php @@ -4,6 +4,7 @@ namespace um\common; use WP_Error; use WP_Session_Tokens; use WP_User; +use WP_User_Query; if ( ! defined( 'ABSPATH' ) ) { exit; @@ -19,6 +20,68 @@ class Users { public function hooks() { add_filter( 'user_has_cap', array( &$this, 'map_caps_by_role' ), 10, 3 ); add_filter( 'editable_roles', array( &$this, 'restrict_roles' ) ); + + add_action( 'init', array( &$this, 'um_schedule_account_status_check' ) ); + add_action( 'um_schedule_account_status_check', array( &$this, 'status_check' ) ); + add_action( 'um_check_account_status_batch', array( &$this, 'batch_check' ), 10, 2 ); + } + + public function um_schedule_account_status_check() { + $interval = 3600; + + if ( ! as_next_scheduled_action( 'um_schedule_account_status_check' ) ) { + UM()->maybe_action_scheduler()->schedule_recurring_action( + time() + 60, + $interval, + 'um_schedule_account_status_check' + ); + } + } + + public function status_check() { + global $wpdb; + $batch_size = 50; + $total_users = $wpdb->get_var( + "SELECT COUNT(u.ID) + FROM {$wpdb->users} u + LEFT JOIN {$wpdb->usermeta} um ON u.ID = um.user_id AND um.meta_key = 'account_status' + WHERE um.meta_value IS NULL OR um.meta_value = ''" + ); + + if ( absint( $total_users ) > 0 ) { + for ( $offset = 0; $offset < absint( $total_users ); $offset += $batch_size ) { + UM()->maybe_action_scheduler()->enqueue_async_action( 'um_check_account_status_batch', array( $offset, $batch_size ) ); + } + } + } + + public function batch_check( $offset, $limit ) { + $users = new WP_User_Query( + array( + 'number' => $limit, + 'offset' => $offset, + 'fields' => array( 'ID' ), + 'meta_query' => array( + 'relation' => 'OR', + array( + 'key' => 'account_status', + 'compare' => 'NOT EXISTS', + ), + array( + 'key' => 'account_status', + 'value' => '', + 'compare' => '=', + ), + ), + ) + ); + $results = $users->get_results(); + + if ( ! empty( $results ) ) { + foreach ( $results as $user ) { + update_user_meta( $user->ID, 'account_status', 'approved' ); + } + } } /** From 4dc4b7497c90b2d5ea10b9285b7cb6518ebc729b Mon Sep 17 00:00:00 2001 From: ashubawork Date: Tue, 8 Apr 2025 13:55:32 +0300 Subject: [PATCH 06/26] - small CSS fixes for directory block --- assets/css/um-members.css | 3 ++- assets/sass/um-members.sass | 3 ++- includes/admin/class-enqueue.php | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/assets/css/um-members.css b/assets/css/um-members.css index edecd6cc..076cf73b 100644 --- a/assets/css/um-members.css +++ b/assets/css/um-members.css @@ -582,6 +582,7 @@ body #content .um-directory a:hover, body #content .um-directory a:focus { background-color: #eee; box-sizing: border-box; position: relative; + min-height: 50px; } .um-directory .um-members-wrapper .um-members.um-members-grid .um-member .um-member-cover[data-ratio] { overflow-y: hidden; @@ -1248,4 +1249,4 @@ body #content .um-directory a:hover, body #content .um-directory a:focus { .select2-container .select2-results li.select2-results__option[aria-disabled=true] { display: none; -} \ No newline at end of file +} diff --git a/assets/sass/um-members.sass b/assets/sass/um-members.sass index b7a5c602..01301d33 100644 --- a/assets/sass/um-members.sass +++ b/assets/sass/um-members.sass @@ -435,6 +435,7 @@ body background-color: #eee box-sizing: border-box position: relative + min-height: 50px &[data-ratio] overflow-y: hidden .um-member-cover-e @@ -1005,4 +1006,4 @@ body .select2-container .select2-results li.select2-results__option[aria-disabled=true] - display: none \ No newline at end of file + display: none diff --git a/includes/admin/class-enqueue.php b/includes/admin/class-enqueue.php index 05c43246..51a53193 100644 --- a/includes/admin/class-enqueue.php +++ b/includes/admin/class-enqueue.php @@ -198,6 +198,7 @@ final class Enqueue extends \um\common\Enqueue { wp_register_style( 'um_responsive', $css_url . 'um-responsive' . $suffix . '.css', array( 'um_profile' ), UM_VERSION ); wp_register_style( 'um_account', $css_url . 'um-account' . $suffix . '.css', array(), UM_VERSION ); wp_register_style( 'um_default_css', $css_url . 'um-old-default' . $suffix . '.css', array(), UM_VERSION ); + wp_register_style( 'um_misc', $css_url . 'um-misc' . $suffix . '.css', array( 'um_styles' ), UM_VERSION ); wp_register_style( 'um_datetime', $libs_url . 'pickadate/default' . $suffix . '.css', array(), '3.6.2' ); wp_register_style( 'um_datetime_date', $libs_url . 'pickadate/default.date' . $suffix . '.css', array( 'um_datetime' ), '3.6.2' ); @@ -209,6 +210,7 @@ final class Enqueue extends \um\common\Enqueue { wp_enqueue_style( 'um_profile' ); wp_enqueue_style( 'um_responsive' ); wp_enqueue_style( 'um_account' ); + wp_enqueue_style( 'um_misc' ); wp_enqueue_style( 'um_datetime_date' ); wp_enqueue_style( 'um_datetime_time' ); From 42e6e71e65dcef22a8aed7c1c631369db9d4c6f2 Mon Sep 17 00:00:00 2001 From: ashubawork Date: Thu, 10 Apr 2025 11:07:13 +0300 Subject: [PATCH 07/26] - add auto approval after the wp-admin registration --- includes/admin/core/class-admin-settings.php | 10 ++++++++++ includes/class-config.php | 1 + includes/core/um-actions-register.php | 10 ++++++++++ 3 files changed, 21 insertions(+) diff --git a/includes/admin/core/class-admin-settings.php b/includes/admin/core/class-admin-settings.php index 7c51275d..a0969df6 100644 --- a/includes/admin/core/class-admin-settings.php +++ b/includes/admin/core/class-admin-settings.php @@ -753,6 +753,9 @@ if ( ! class_exists( 'um\admin\core\Admin_Settings' ) ) { 'use_um_gravatar_default_image' => array( 'sanitize' => 'bool', ), + 'auto_user_approve' => array( + 'sanitize' => 'bool', + ), 'delete_comments' => array( 'sanitize' => 'bool', ), @@ -1182,6 +1185,13 @@ if ( ! class_exists( 'um\admin\core\Admin_Settings' ) ) { 'description' => __( 'Do you want to use the plugin default avatar instead of the gravatar default photo (If the user did not upload a custom profile photo/avatar).', 'ultimate-member' ), 'conditional' => array( 'use_um_gravatar_default_builtin_image', '=', 'default' ), ), + array( + 'id' => 'auto_user_approve', + 'type' => 'checkbox', + 'label' => __( 'Ignore the "User Role > Registration Options"', 'ultimate-member' ), + 'checkbox_label' => __( 'Automatically approve users from the wp-admin dashboard', 'ultimate-member' ), + 'description' => __( 'Ignore registration settings and automatically approve the user if this user is added from the wp-admin dashboard.', 'ultimate-member' ), + ), array( 'id' => 'delete_comments', 'type' => 'checkbox', diff --git a/includes/class-config.php b/includes/class-config.php index d7fa2aea..97c6aec1 100644 --- a/includes/class-config.php +++ b/includes/class-config.php @@ -658,6 +658,7 @@ if ( ! class_exists( 'um\Config' ) ) { 'use_gravatars' => false, 'use_um_gravatar_default_builtin_image' => 'default', 'use_um_gravatar_default_image' => false, + 'auto_user_approve' => false, 'toggle_password' => false, 'require_strongpass' => false, 'password_min_chars' => 8, diff --git a/includes/core/um-actions-register.php b/includes/core/um-actions-register.php index 0516d902..bea94795 100644 --- a/includes/core/um-actions-register.php +++ b/includes/core/um-actions-register.php @@ -113,6 +113,10 @@ add_action( 'um_user_register', 'um_after_insert_user', 1, 3 ); * @param $user_id */ function um_send_registration_notification( $user_id ) { + if ( is_admin() && UM()->options()->get( 'auto_user_approve' ) ) { + return; + } + um_fetch_user( $user_id ); $registration_status = um_user( 'status' ); @@ -138,6 +142,12 @@ add_action( 'um_registration_complete', 'um_send_registration_notification' ); */ function um_check_user_status( $user_id, $args, $form_data = null ) { $registration_status = um_user( 'status' ); + + if ( ( is_null( $form_data ) || is_admin() ) && UM()->options()->get( 'auto_user_approve' ) ) { + UM()->common()->users()->set_status( $user_id, 'approved' ); + return; + } + /** * Fires after complete UM user registration. * Where $status can be equal to 'approved', 'checkmail' or 'pending'. From 661aa2060fa38f50d4ec972d9726d6942530f9ee Mon Sep 17 00:00:00 2001 From: yuriinalivaiko Date: Fri, 11 Apr 2025 16:04:55 +0300 Subject: [PATCH 08/26] - fixed a member directory 'select' filter query --- includes/core/class-member-directory-meta.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/core/class-member-directory-meta.php b/includes/core/class-member-directory-meta.php index 9ab8b16b..272fbe0d 100644 --- a/includes/core/class-member-directory-meta.php +++ b/includes/core/class-member-directory-meta.php @@ -340,11 +340,11 @@ if ( ! class_exists( 'um\core\Member_Directory_Meta' ) ) { // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $join_alias and $compare variables are pre-escaped. $values_array[] = $wpdb->prepare( "{$join_alias}.um_value LIKE %s", '%"' . $wpdb->esc_like( $single_val ) . '"%' ); - $values_array[] = $wpdb->prepare( "{$join_alias}.um_value LIKE %s", '%' . $wpdb->esc_like( maybe_serialize( (string) $single_val ) ) . '%' ); + $values_array[] = $wpdb->prepare( "{$join_alias}.um_value LIKE %s", '%' . $wpdb->esc_like( serialize( (string) $single_val ) ) . '%' ); $values_array[] = $wpdb->prepare( "{$join_alias}.um_value = %s", $single_val ); if ( is_numeric( $single_val ) ) { - $values_array[] = $wpdb->prepare( "{$join_alias}.um_value LIKE %s", '%' . $wpdb->esc_like( maybe_serialize( (int) $single_val ) ) . '%' ); + $values_array[] = $wpdb->prepare( "{$join_alias}.um_value LIKE %s", '%' . $wpdb->esc_like( serialize( (int) $single_val ) ) . '%' ); } // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $join_alias is pre-escaped. } From 8ef597ad74a3b775ab467f5886b2bdc9afbe43b2 Mon Sep 17 00:00:00 2001 From: ashubawork Date: Mon, 14 Apr 2025 14:21:41 +0300 Subject: [PATCH 09/26] - fix banned fields and site health status --- includes/admin/class-site-health.php | 135 +++++++++++++++++++++++++++ includes/core/class-builtin.php | 2 +- includes/core/class-user.php | 3 + 3 files changed, 139 insertions(+), 1 deletion(-) diff --git a/includes/admin/class-site-health.php b/includes/admin/class-site-health.php index bc1a3558..f8405802 100644 --- a/includes/admin/class-site-health.php +++ b/includes/admin/class-site-health.php @@ -48,6 +48,20 @@ class Site_Health { ); } + $custom_fields = get_option( 'um_fields', array() ); + if ( ! empty( $custom_fields ) ) { + $keys = array_merge( UM()->builtin()->blacklist_fields, UM()->user()->banned_keys ); + foreach ( $keys as $key ) { + if ( isset( $custom_fields[ $key ] ) ) { + $tests['direct']['um_banned_fields'] = array( + 'label' => esc_html__( 'Are the banned custom fields?', 'ultimate-member' ), + 'test' => array( $this, 'banned_fields_test' ), + ); + break; + } + } + } + return $tests; } @@ -210,6 +224,127 @@ class Site_Health { return $result; } + public function get_banned_fields() { + $keys = array_merge( UM()->builtin()->blacklist_fields, UM()->user()->banned_keys ); + + $result = array( + 'description' => '', + 'actions' => '', + ); + + $forms = get_posts( + array( + 'post_type' => 'um_form', + 'posts_per_page' => -1, + 'fields' => 'ids', + ) + ); + + $forms_count = 0; + $break_forms = array(); + if ( ! empty( $forms ) ) { + foreach ( $forms as $form_id ) { + $fields = UM()->query()->get_attr( 'custom_fields', $form_id ); + if ( empty( $fields ) ) { + continue; + } + foreach ( $fields as $field ) { + if ( empty( $field['metakey'] ) ) { + continue; + } + if ( in_array( $field['metakey'], $keys, true ) ) { + $break_forms[] = array( + 'id' => $form_id, + 'title' => get_the_title( $form_id ), + 'link' => get_edit_post_link( $form_id ), + 'key' => $field['metakey'], + ); + ++$forms_count; + } + } + } + } + + if ( 0 < $forms_count ) { + $result['description'] .= sprintf( + '

%s

', + __( 'Your fields in the Ultimate Member Forms are banned.', 'ultimate-member' ) + ); + + if ( ! empty( $break_forms ) ) { + $result['description'] .= sprintf( + '

%s', + __( 'Related to Ultimate Member Forms: ', 'ultimate-member' ) + ); + + $form_links = array(); + foreach ( $break_forms as $break_form ) { + $form_links[] = sprintf( + '%s in %s (#ID: %s)', + esc_url( $break_form['link'] ), + esc_html__( 'field', 'ultimate-member' ) . ' "' . esc_html( $break_form['key'] ) . '"', + esc_html( $break_form['title'] ), + esc_html( $break_form['id'] ) + ); + } + + $result['description'] .= sprintf( + '%s


', + implode( ', ', $form_links ) + ); + } + + $result['actions'] .= sprintf( + '

%s

', + admin_url( 'edit.php?post_type=um_form' ), + esc_html__( 'Edit form fields and update', 'ultimate-member' ) + ); + } + + $result = apply_filters( 'um_get_banned_fields_result', $result, $keys ); + + if ( ! empty( $result['description'] ) ) { + $result['description'] .= sprintf( + '

%s

', + __( 'Using banned meta keys may break the website\'s functionality.', 'ultimate-member' ) + ); + } + + if ( ! empty( $result['description'] ) && ! empty( $result['actions'] ) ) { + return $result; + } + + return false; + } + + public function banned_fields_test() { + $result = array( + 'label' => __( 'You have correct Ultimate Member fields', 'ultimate-member' ), + 'status' => 'good', + 'badge' => array( + 'label' => UM_PLUGIN_NAME, + 'color' => self::BADGE_COLOR, + ), + 'description' => sprintf( + '

%s

', + __( 'Your all custom Ultimate Member fields are correct.', 'ultimate-member' ) + ), + 'actions' => '', + 'test' => 'um_banned_fields', + ); + + $banned_fields = $this->get_banned_fields(); + if ( false !== $banned_fields ) { + $result['label'] = __( 'Some field from Ultimate Member forms has banned meta key', 'ultimate-member' ); + $result['status'] = 'recommended'; + $result['badge']['color'] = 'orange'; + $result['description'] = $banned_fields['description']; + $result['actions'] = $banned_fields['actions']; + } + + return $result; + } + private function get_roles() { return UM()->roles()->get_roles(); } diff --git a/includes/core/class-builtin.php b/includes/core/class-builtin.php index a0fd52c4..aa0fb5c0 100644 --- a/includes/core/class-builtin.php +++ b/includes/core/class-builtin.php @@ -183,7 +183,7 @@ if ( ! class_exists( 'um\core\Builtin' ) ) { * @return int|string Empty or error string. */ public function blacklist_field_err( $key ) { - if ( in_array( strtolower( $key ), $this->blacklist_fields, true ) ) { + if ( in_array( strtolower( $key ), $this->blacklist_fields, true ) || in_array( strtolower( $key ), UM()->user()->banned_keys, true ) ) { return __( 'Your meta key can not be used', 'ultimate-member' ); } diff --git a/includes/core/class-user.php b/includes/core/class-user.php index f8d70584..ea358262 100644 --- a/includes/core/class-user.php +++ b/includes/core/class-user.php @@ -93,6 +93,9 @@ if ( ! class_exists( 'um\core\User' ) ) { $this->banned_keys = array( 'metabox', + 'user_id', + 'role', + 'user_pass', 'postbox', 'meta-box', 'dismissed_wp_pointers', From 9f54338c0a8efe2d7d91f9cce4fea087b1769d6b Mon Sep 17 00:00:00 2001 From: Mykyta Synelnikov Date: Mon, 14 Apr 2025 15:02:02 +0300 Subject: [PATCH 10/26] Fix member grid initialization for specific container check Added a condition to initialize UM_Member_Grid only when the directory has the 'um-members-grid' class. This avoids unintended initialization and ensures proper functionality. --- blocks-src/um-member-directories/src/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/blocks-src/um-member-directories/src/index.js b/blocks-src/um-member-directories/src/index.js index c5415fff..d7fd056d 100644 --- a/blocks-src/um-member-directories/src/index.js +++ b/blocks-src/um-member-directories/src/index.js @@ -89,7 +89,9 @@ jQuery(window).on( 'load', function($) { jQuery(mutation.addedNodes).find('.um-member').each(function() { let wrapper = document.querySelector('.um-member'); let directory = jQuery(this).parent(); - UM_Member_Grid(directory); + if (directory.hasClass('um-members-grid')) { + UM_Member_Grid(directory); + } if (wrapper) { wrapper.addEventListener('click', (event) => { From 0cac6179e048491db085524bcdbf9b9315e734a8 Mon Sep 17 00:00:00 2001 From: ashubawork Date: Mon, 14 Apr 2025 15:14:39 +0300 Subject: [PATCH 11/26] - blocks build --- includes/blocks/um-member-directories/src/index.asset.php | 2 +- includes/blocks/um-member-directories/src/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/blocks/um-member-directories/src/index.asset.php b/includes/blocks/um-member-directories/src/index.asset.php index 7ac488fc..3b988c76 100644 --- a/includes/blocks/um-member-directories/src/index.asset.php +++ b/includes/blocks/um-member-directories/src/index.asset.php @@ -1 +1 @@ - array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-server-side-render'), 'version' => 'cced871823424b7be338'); + array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-server-side-render'), 'version' => 'b29cf253331968aa1d87'); diff --git a/includes/blocks/um-member-directories/src/index.js b/includes/blocks/um-member-directories/src/index.js index 38fffc0c..4b7cc8fd 100644 --- a/includes/blocks/um-member-directories/src/index.js +++ b/includes/blocks/um-member-directories/src/index.js @@ -1 +1 @@ -(()=>{"use strict";var e={n:t=>{var r=t&&t.__esModule?()=>t.default:()=>t;return e.d(r,{a:r}),r},d:(t,r)=>{for(var i in r)e.o(r,i)&&!e.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:r[i]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)};const t=window.wp.data,r=window.wp.components,i=window.wp.blockEditor,n=window.wp.serverSideRender;var o=e.n(n);const l=window.wp.blocks,a=window.wp.element,c=window.ReactJSXRuntime;(0,l.registerBlockType)("um-block/um-member-directories",{edit:function(e){const n=(0,i.useBlockProps)(),{attributes:l,setAttributes:d}=e,{member_id:u}=l,m=(0,t.useSelect)((e=>e("core").getEntityRecords("postType","um_directory",{per_page:-1,_fields:["id","title"]})),[]),s=(0,a.useMemo)((()=>m?0===m.length?[{label:wp.i18n.__("No posts found.","ultimate-member"),value:""}]:[{label:wp.i18n.__("Select Directory","ultimate-member"),value:""}].concat(m.map((e=>({label:e.title.rendered,value:e.id})))):[{label:wp.i18n.__("Loading...","ultimate-member"),value:""}]),[m]);return m?(0,c.jsxs)("div",{...n,children:[(0,c.jsx)(o(),{block:"um-block/um-member-directories",attributes:l}),(0,c.jsx)(i.InspectorControls,{children:(0,c.jsx)(r.PanelBody,{title:wp.i18n.__("Select Directories","ultimate-member"),children:(0,c.jsx)(r.SelectControl,{label:wp.i18n.__("Select Directories","ultimate-member"),className:"um_select_directory",value:u,options:s,onChange:e=>d({member_id:e})})})})]}):(0,c.jsxs)("p",{children:[(0,c.jsx)(r.Spinner,{}),wp.i18n.__("Loading...","ultimate-member")]})},save:()=>null}),jQuery(window).on("load",(function(e){new MutationObserver((function(e){e.forEach((function(e){jQuery(e.addedNodes).find(".um.um-directory").each((function(){let e=document.querySelector(".um-directory"),t=jQuery(this);um_ajax_get_members(t),um_slider_filter_init(t),um_datepicker_filter_init(t),um_timepicker_filter_init(t),e&&e.addEventListener("click",(t=>{t.target!==e&&(t.preventDefault(),t.stopPropagation())}))})),jQuery(e.addedNodes).find(".um-member").each((function(){let e=document.querySelector(".um-member"),t=jQuery(this).parent();UM_Member_Grid(t),e&&e.addEventListener("click",(t=>{t.target!==e&&(t.preventDefault(),t.stopPropagation())}))}))}))})).observe(document,{attributes:!1,childList:!0,characterData:!1,subtree:!0})}))})(); \ No newline at end of file +(()=>{"use strict";var e={n:t=>{var r=t&&t.__esModule?()=>t.default:()=>t;return e.d(r,{a:r}),r},d:(t,r)=>{for(var i in r)e.o(r,i)&&!e.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:r[i]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)};const t=window.wp.data,r=window.wp.components,i=window.wp.blockEditor,n=window.wp.serverSideRender;var o=e.n(n);const l=window.wp.blocks,a=window.wp.element,c=window.ReactJSXRuntime;(0,l.registerBlockType)("um-block/um-member-directories",{edit:function(e){const n=(0,i.useBlockProps)(),{attributes:l,setAttributes:d}=e,{member_id:s}=l,u=(0,t.useSelect)((e=>e("core").getEntityRecords("postType","um_directory",{per_page:-1,_fields:["id","title"]})),[]),m=(0,a.useMemo)((()=>u?0===u.length?[{label:wp.i18n.__("No posts found.","ultimate-member"),value:""}]:[{label:wp.i18n.__("Select Directory","ultimate-member"),value:""}].concat(u.map((e=>({label:e.title.rendered,value:e.id})))):[{label:wp.i18n.__("Loading...","ultimate-member"),value:""}]),[u]);return u?(0,c.jsxs)("div",{...n,children:[(0,c.jsx)(o(),{block:"um-block/um-member-directories",attributes:l}),(0,c.jsx)(i.InspectorControls,{children:(0,c.jsx)(r.PanelBody,{title:wp.i18n.__("Select Directories","ultimate-member"),children:(0,c.jsx)(r.SelectControl,{label:wp.i18n.__("Select Directories","ultimate-member"),className:"um_select_directory",value:s,options:m,onChange:e=>d({member_id:e})})})})]}):(0,c.jsxs)("p",{children:[(0,c.jsx)(r.Spinner,{}),wp.i18n.__("Loading...","ultimate-member")]})},save:()=>null}),jQuery(window).on("load",(function(e){new MutationObserver((function(e){e.forEach((function(e){jQuery(e.addedNodes).find(".um.um-directory").each((function(){let e=document.querySelector(".um-directory"),t=jQuery(this);um_ajax_get_members(t),um_slider_filter_init(t),um_datepicker_filter_init(t),um_timepicker_filter_init(t),e&&e.addEventListener("click",(t=>{t.target!==e&&(t.preventDefault(),t.stopPropagation())}))})),jQuery(e.addedNodes).find(".um-member").each((function(){let e=document.querySelector(".um-member"),t=jQuery(this).parent();t.hasClass("um-members-grid")&&UM_Member_Grid(t),e&&e.addEventListener("click",(t=>{t.target!==e&&(t.preventDefault(),t.stopPropagation())}))}))}))})).observe(document,{attributes:!1,childList:!0,characterData:!1,subtree:!0})}))})(); \ No newline at end of file From 287aeb12e8fe98cdad35dc30bcfcd3aeec832110 Mon Sep 17 00:00:00 2001 From: Mykyta Synelnikov Date: Mon, 14 Apr 2025 15:30:02 +0300 Subject: [PATCH 12/26] Update Ultimate Member plugin to version 2.10.3 Bump stable version and update tested WordPress compatibility. Includes Action Scheduler refactor, bug fixes for Member Directory styles and filtering, and a PHP Warning fix. Cached assets should be regenerated after updating. --- readme.txt | 14 ++++++++++++-- ultimate-member.php | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/readme.txt b/readme.txt index 688735d0..fdeb56ec 100644 --- a/readme.txt +++ b/readme.txt @@ -5,7 +5,7 @@ Contributors: ultimatemember, champsupertramp, nsinelnikov Tags: community, member, membership, user-profile, user-registration Requires PHP: 7.0 Requires at least: 6.2 -Tested up to: 6.7 +Tested up to: 6.8 Stable tag: 2.10.2 License: GPLv3 License URI: http://www.gnu.org/licenses/gpl-3.0.txt @@ -167,9 +167,19 @@ 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.10.3 2025-04-xx = += 2.10.3 2025-04-15 = +**Enhancements** +* 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. + +**Cached and optimized/minified assets(JS/CSS) must be flushed/re-generated after upgrade** = 2.10.2 2025-04-02 = diff --git a/ultimate-member.php b/ultimate-member.php index 3b702a32..66b8d5dd 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.10.3-alpha + * Version: 2.10.3 * Author: Ultimate Member * Author URI: http://ultimatemember.com/ * Text Domain: ultimate-member From f6005f2e9d31a1846ab59103cd5385a3a74ea08e Mon Sep 17 00:00:00 2001 From: Mykyta Synelnikov Date: Mon, 14 Apr 2025 15:45:07 +0300 Subject: [PATCH 13/26] Prevent admin notifications and refine user registration logic Updated the registration process to avoid sending notifications for admin-created users. Simplified and clarified user status handling, ensuring consistent behavior across frontend and admin actions. --- includes/core/um-actions-register.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/includes/core/um-actions-register.php b/includes/core/um-actions-register.php index bea94795..0dd11a1b 100644 --- a/includes/core/um-actions-register.php +++ b/includes/core/um-actions-register.php @@ -113,7 +113,8 @@ add_action( 'um_user_register', 'um_after_insert_user', 1, 3 ); * @param $user_id */ function um_send_registration_notification( $user_id ) { - if ( is_admin() && UM()->options()->get( 'auto_user_approve' ) ) { + if ( is_admin() ) { + // Don't send email notifications to administrators about new user registration, because the user was created from wp-admin. return; } @@ -141,11 +142,10 @@ add_action( 'um_registration_complete', 'um_send_registration_notification' ); * @param null|array $form_data */ function um_check_user_status( $user_id, $args, $form_data = null ) { - $registration_status = um_user( 'status' ); - if ( ( is_null( $form_data ) || is_admin() ) && UM()->options()->get( 'auto_user_approve' ) ) { - UM()->common()->users()->set_status( $user_id, 'approved' ); - return; + $registration_status = 'approved'; + } else { + $registration_status = um_user( 'status' ); } /** @@ -182,6 +182,7 @@ function um_check_user_status( $user_id, $args, $form_data = null ) { if ( is_null( $form_data ) || is_admin() ) { return; } + // Code below is running only for registration from the frontend forms. /** * Fires after complete UM user registration. Only for the frontend action which is run before autologin and redirects. From 695d522cb7220df9d80ae034fe7dbc6c916f9ff6 Mon Sep 17 00:00:00 2001 From: Mykyta Synelnikov Date: Mon, 14 Apr 2025 16:04:29 +0300 Subject: [PATCH 14/26] Rename 'auto_user_approve' to 'admin_ignore_user_status'. Updated the setting name across relevant files to improve clarity and better reflect its functionality. Adjusted related logic to ensure consistent behavior with the new naming. --- includes/admin/core/class-admin-settings.php | 4 ++-- includes/class-config.php | 2 +- includes/core/um-actions-register.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/includes/admin/core/class-admin-settings.php b/includes/admin/core/class-admin-settings.php index a0969df6..bdef709a 100644 --- a/includes/admin/core/class-admin-settings.php +++ b/includes/admin/core/class-admin-settings.php @@ -753,7 +753,7 @@ if ( ! class_exists( 'um\admin\core\Admin_Settings' ) ) { 'use_um_gravatar_default_image' => array( 'sanitize' => 'bool', ), - 'auto_user_approve' => array( + 'admin_ignore_user_status' => array( 'sanitize' => 'bool', ), 'delete_comments' => array( @@ -1186,7 +1186,7 @@ if ( ! class_exists( 'um\admin\core\Admin_Settings' ) ) { 'conditional' => array( 'use_um_gravatar_default_builtin_image', '=', 'default' ), ), array( - 'id' => 'auto_user_approve', + 'id' => 'admin_ignore_user_status', 'type' => 'checkbox', 'label' => __( 'Ignore the "User Role > Registration Options"', 'ultimate-member' ), 'checkbox_label' => __( 'Automatically approve users from the wp-admin dashboard', 'ultimate-member' ), diff --git a/includes/class-config.php b/includes/class-config.php index 97c6aec1..f77a229f 100644 --- a/includes/class-config.php +++ b/includes/class-config.php @@ -658,7 +658,7 @@ if ( ! class_exists( 'um\Config' ) ) { 'use_gravatars' => false, 'use_um_gravatar_default_builtin_image' => 'default', 'use_um_gravatar_default_image' => false, - 'auto_user_approve' => false, + 'admin_ignore_user_status' => false, 'toggle_password' => false, 'require_strongpass' => false, 'password_min_chars' => 8, diff --git a/includes/core/um-actions-register.php b/includes/core/um-actions-register.php index 0dd11a1b..29a73e1a 100644 --- a/includes/core/um-actions-register.php +++ b/includes/core/um-actions-register.php @@ -142,7 +142,7 @@ add_action( 'um_registration_complete', 'um_send_registration_notification' ); * @param null|array $form_data */ function um_check_user_status( $user_id, $args, $form_data = null ) { - if ( ( is_null( $form_data ) || is_admin() ) && UM()->options()->get( 'auto_user_approve' ) ) { + if ( ( is_null( $form_data ) || is_admin() ) && UM()->options()->get( 'admin_ignore_user_status' ) ) { $registration_status = 'approved'; } else { $registration_status = um_user( 'status' ); From 4cb1a2a2e27bd88378efbac0d7db97d82e5b5e72 Mon Sep 17 00:00:00 2001 From: Mykyta Synelnikov Date: Mon, 14 Apr 2025 16:19:06 +0300 Subject: [PATCH 15/26] Add new user role setting and tweak admin email notifications Introduced a setting to bypass "User Role > Registration Options" for auto-approving users created via wp-admin. Also adjusted admin email notifications to prevent alerts for user registrations made through wp-admin. --- readme.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readme.txt b/readme.txt index fdeb56ec..717c7564 100644 --- a/readme.txt +++ b/readme.txt @@ -171,6 +171,8 @@ IMPORTANT: PLEASE UPDATE THE PLUGIN TO AT LEAST VERSION 2.6.7 IMMEDIATELY. VERSI **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** From c8bd79fcebc0c604a80f2ad614ebdfd893091589 Mon Sep 17 00:00:00 2001 From: Mykyta Synelnikov Date: Mon, 14 Apr 2025 23:12:29 +0300 Subject: [PATCH 16/26] Add scheduled user account status check and improve approval Introduce a new `Users` class to handle scheduled tasks for user status checks and batch processing. Refactor user approval functionality to allow silent operations and avoid sending notifications where unnecessary. Enhance user registration to prevent unfinished registrations from being processed in scheduled checks. --- includes/common/actions/class-users.php | 113 ++++++++++++++++++++++++ includes/common/class-init.php | 4 + includes/common/class-users.php | 99 +++++---------------- includes/core/um-actions-register.php | 7 ++ 4 files changed, 144 insertions(+), 79 deletions(-) create mode 100644 includes/common/actions/class-users.php diff --git a/includes/common/actions/class-users.php b/includes/common/actions/class-users.php new file mode 100644 index 00000000..dae0ce7d --- /dev/null +++ b/includes/common/actions/class-users.php @@ -0,0 +1,113 @@ +maybe_action_scheduler()->next_scheduled_action( self::SCHEDULE_ACTION ) ) { + return; + } + + UM()->maybe_action_scheduler()->schedule_recurring_action( + time() + 60, + self::INTERVAL, + self::SCHEDULE_ACTION + ); + } + + public function status_check() { + global $wpdb; + $total_users = $wpdb->get_var( + "SELECT COUNT(u.ID) + FROM {$wpdb->users} u + LEFT JOIN {$wpdb->usermeta} um ON u.ID = um.user_id AND um.meta_key = 'account_status' + LEFT JOIN {$wpdb->usermeta} um2 ON u.ID = um2.user_id AND um2.meta_key = 'um_registration_in_progress' + WHERE ( um.meta_value IS NULL OR um.meta_value = '' ) AND + um2.meta_value IS NULL OR um2.meta_value != '1'" + ); + $total_users = absint( $total_users ); + + if ( empty( $total_users ) ) { + return; + } + + for ( $offset = 0; $offset < $total_users; $offset += self::BATCH_SIZE ) { + UM()->maybe_action_scheduler()->enqueue_async_action( self::BATCH_ACTION, array( $offset ) ); + } + } + + public function batch_check( $offset ) { + $users = new WP_User_Query( + array( + 'number' => self::BATCH_SIZE, + 'offset' => $offset, + 'fields' => 'ids', + 'meta_query' => array( + 'relation' => 'AND', + array( + 'relation' => 'OR', + array( + 'key' => 'um_registration_in_progress', + 'compare' => 'NOT EXISTS', + ), + array( + 'key' => 'um_registration_in_progress', + 'value' => '1', + 'compare' => '!=', + ), + ), + array( + 'relation' => 'OR', + array( + 'key' => 'account_status', + 'compare' => 'NOT EXISTS', + ), + array( + 'key' => 'account_status', + 'value' => '', + 'compare' => '=', + ), + ), + ), + ) + ); + + $results = $users->get_results(); + + if ( ! empty( $results ) ) { + foreach ( $results as $user_id ) { + UM()->common()->users()->approve( $user_id, true, true ); + } + } + } + } +} diff --git a/includes/common/class-init.php b/includes/common/class-init.php index 73cf4805..2ceff24f 100644 --- a/includes/common/class-init.php +++ b/includes/common/class-init.php @@ -39,6 +39,10 @@ if ( ! class_exists( 'um\common\Init' ) ) { if ( empty( UM()->classes['um\common\actions\emails'] ) ) { UM()->classes['um\common\actions\emails'] = new actions\Emails(); } + + if ( empty( UM()->classes['um\common\actions\users'] ) ) { + UM()->classes['um\common\actions\users'] = new actions\Users(); + } // Other classes init here as soon as possible. } diff --git a/includes/common/class-users.php b/includes/common/class-users.php index a2c2ad83..8b2ee5aa 100644 --- a/includes/common/class-users.php +++ b/includes/common/class-users.php @@ -20,68 +20,6 @@ class Users { public function hooks() { add_filter( 'user_has_cap', array( &$this, 'map_caps_by_role' ), 10, 3 ); add_filter( 'editable_roles', array( &$this, 'restrict_roles' ) ); - - add_action( 'init', array( &$this, 'um_schedule_account_status_check' ) ); - add_action( 'um_schedule_account_status_check', array( &$this, 'status_check' ) ); - add_action( 'um_check_account_status_batch', array( &$this, 'batch_check' ), 10, 2 ); - } - - public function um_schedule_account_status_check() { - $interval = 3600; - - if ( ! as_next_scheduled_action( 'um_schedule_account_status_check' ) ) { - UM()->maybe_action_scheduler()->schedule_recurring_action( - time() + 60, - $interval, - 'um_schedule_account_status_check' - ); - } - } - - public function status_check() { - global $wpdb; - $batch_size = 50; - $total_users = $wpdb->get_var( - "SELECT COUNT(u.ID) - FROM {$wpdb->users} u - LEFT JOIN {$wpdb->usermeta} um ON u.ID = um.user_id AND um.meta_key = 'account_status' - WHERE um.meta_value IS NULL OR um.meta_value = ''" - ); - - if ( absint( $total_users ) > 0 ) { - for ( $offset = 0; $offset < absint( $total_users ); $offset += $batch_size ) { - UM()->maybe_action_scheduler()->enqueue_async_action( 'um_check_account_status_batch', array( $offset, $batch_size ) ); - } - } - } - - public function batch_check( $offset, $limit ) { - $users = new WP_User_Query( - array( - 'number' => $limit, - 'offset' => $offset, - 'fields' => array( 'ID' ), - 'meta_query' => array( - 'relation' => 'OR', - array( - 'key' => 'account_status', - 'compare' => 'NOT EXISTS', - ), - array( - 'key' => 'account_status', - 'value' => '', - 'compare' => '=', - ), - ), - ) - ); - $results = $users->get_results(); - - if ( ! empty( $results ) ) { - foreach ( $results as $user ) { - update_user_meta( $user->ID, 'account_status', 'approved' ); - } - } } /** @@ -721,11 +659,12 @@ class Users { * * @param int $user_id User ID. * @param bool $force If true - ignore current user condition. + * @param bool $silent If true - don't send email notification. E.g. case when user already exists, but doesn't have a status. * * @return bool `true` if the user has been approved * `false` on failure or if the user already has approved status. */ - public function approve( $user_id, $force = false ) { + public function approve( $user_id, $force = false, $silent = false ) { if ( ! $this->can_be_approved( $user_id, $force ) ) { return false; } @@ -746,25 +685,27 @@ class Users { // It's `false` on failure or if the user already has approved status. if ( false !== $result ) { - $userdata = get_userdata( $user_id ); + if ( false === $silent ) { + $userdata = get_userdata( $user_id ); - $this->reset_activation_link( $user_id ); + $this->reset_activation_link( $user_id ); - $email_slug = 'welcome_email'; - if ( 'awaiting_admin_review' === $old_status ) { - $email_slug = 'approved_email'; - $this->maybe_generate_password_reset_key( $userdata ); + $email_slug = 'welcome_email'; + if ( 'awaiting_admin_review' === $old_status ) { + $email_slug = 'approved_email'; + $this->maybe_generate_password_reset_key( $userdata ); + } + + $current_user_id = get_current_user_id(); + um_fetch_user( $user_id ); + + add_filter( 'um_template_tags_patterns_hook', array( UM()->password(), 'add_placeholder' ) ); + add_filter( 'um_template_tags_replaces_hook', array( UM()->password(), 'add_replace_placeholder' ) ); + + UM()->maybe_action_scheduler()->enqueue_async_action( 'um_dispatch_email', array( $userdata->user_email, $email_slug, array( 'fetch_user_id' => $user_id ) ) ); + + um_fetch_user( $current_user_id ); } - - $current_user_id = get_current_user_id(); - um_fetch_user( $user_id ); - - add_filter( 'um_template_tags_patterns_hook', array( UM()->password(), 'add_placeholder' ) ); - add_filter( 'um_template_tags_replaces_hook', array( UM()->password(), 'add_replace_placeholder' ) ); - - UM()->maybe_action_scheduler()->enqueue_async_action( 'um_dispatch_email', array( $userdata->user_email, $email_slug, array( 'fetch_user_id' => $user_id ) ) ); - - um_fetch_user( $current_user_id ); /** * Fires after User has been approved. * diff --git a/includes/core/um-actions-register.php b/includes/core/um-actions-register.php index 0516d902..2459d56c 100644 --- a/includes/core/um-actions-register.php +++ b/includes/core/um-actions-register.php @@ -169,6 +169,8 @@ function um_check_user_status( $user_id, $args, $form_data = null ) { */ do_action( "um_post_registration_{$registration_status}_hook", $user_id, $args, $form_data ); + delete_user_meta( $user_id, 'um_registration_in_progress' ); // Status is set. We can delete this marker. + if ( is_null( $form_data ) || is_admin() ) { return; } @@ -502,6 +504,11 @@ function um_submit_form_register( $args, $form_data ) { 'user_pass' => $user_password, 'user_email' => $user_email, 'role' => $user_role, + 'meta_input' => array( + // It's used to ignore users who cannot finish the registration process in the scheduled tasks + // to set 'approved' status to the users without `account_status` meta. + 'um_registration_in_progress' => true, + ), ); $user_id = wp_insert_user( $userdata ); From bdd973c14c8361fcf66c4996018b4535a5194dc8 Mon Sep 17 00:00:00 2001 From: Mykyta Synelnikov Date: Mon, 14 Apr 2025 23:13:32 +0300 Subject: [PATCH 17/26] Remove unused WP_User_Query import The WP_User_Query import was removed as it is not used in the code. This helps to clean up unnecessary code and improve maintainability. No functionality is affected by this change. --- includes/common/class-users.php | 1 - 1 file changed, 1 deletion(-) diff --git a/includes/common/class-users.php b/includes/common/class-users.php index 8b2ee5aa..61587997 100644 --- a/includes/common/class-users.php +++ b/includes/common/class-users.php @@ -4,7 +4,6 @@ namespace um\common; use WP_Error; use WP_Session_Tokens; use WP_User; -use WP_User_Query; if ( ! defined( 'ABSPATH' ) ) { exit; From 5356148cc4ff2710e2720f7c2ae82de2f4ca5699 Mon Sep 17 00:00:00 2001 From: Mykyta Synelnikov Date: Tue, 15 Apr 2025 14:27:59 +0300 Subject: [PATCH 18/26] Implement batch processing for users with empty account statuses Introduced a new batch process to handle users lacking an `account_status` meta efficiently. Refactored legacy methods, added async scheduling, and created helper functions to manage and track progress. These changes improve performance and reliability for large user bases. --- includes/admin/class-users-columns.php | 3 - includes/admin/core/class-admin-notices.php | 164 +++++++++++++------- includes/common/actions/class-users.php | 69 +++++--- includes/common/class-users.php | 25 +++ includes/core/class-setup.php | 40 ++--- includes/core/class-user.php | 3 - tests/generate-empty-status-users.php | 34 ++++ uninstall.php | 2 +- 8 files changed, 230 insertions(+), 110 deletions(-) create mode 100644 tests/generate-empty-status-users.php diff --git a/includes/admin/class-users-columns.php b/includes/admin/class-users-columns.php index 81165829..5ad62275 100644 --- a/includes/admin/class-users-columns.php +++ b/includes/admin/class-users-columns.php @@ -350,9 +350,6 @@ if ( ! class_exists( 'um\admin\Users_Columns' ) ) { return; } - // Set default statuses if not already done. - UM()->setup()->set_default_user_status(); - $id = 'um_user_status'; // need to add there additional nonce field because WordPress native _wpnonce field isn't visible on the users.php screen then custom actions diff --git a/includes/admin/core/class-admin-notices.php b/includes/admin/core/class-admin-notices.php index 0fbc12ab..be73e0f4 100644 --- a/includes/admin/core/class-admin-notices.php +++ b/includes/admin/core/class-admin-notices.php @@ -47,6 +47,8 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) { $this->lock_registration(); + $this->empty_status_users(); + $this->extensions_page(); $this->child_theme_required(); @@ -204,17 +206,17 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) { ob_start(); ?> -
- +
+
- array( + 'href' => array(), + ), + 'strong' => array(), + ); + + $this->add_notice( + 'empty_status_users', + array( + 'class' => 'info', + // translators: %1$d: Background update for users is complete; %2$d: Total users for background update. + 'message' => '

' . wp_kses( sprintf( __( 'Background process is running: Setting user statuses %1$d/%2$d.', 'ultimate-member' ), $empty_status_users[0], $empty_status_users[1] ), $allowed_html ) . '

', + 'dismissible' => false, + ) + ); + } + /** * Checking if the "Membership - Anyone can register" WordPress general setting is active */ @@ -256,11 +289,11 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) { ?>

- All Access Pass – Get access to all Ultimate Member extensions at a significant discount with our All Access Pass.', 'ultimate-member' ) ?> + All Access Pass – Get access to all Ultimate Member extensions at a significant discount with our All Access Pass.', 'ultimate-member' ); ?>

- +

@@ -290,7 +323,7 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) { $path = str_replace( '//', '/', $path ); if ( ! file_exists( $path ) ) { - $old = umask(0); + $old = umask( 0 ); @mkdir( $path, 0777, true ); umask( $old ); } @@ -326,9 +359,12 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) { 'woocommerce', ); - $slugs = array_map( function( $item ) { - return 'um-' . $item . '/um-' . $item . '.php'; - }, $old_extensions ); + $slugs = array_map( + function ( $item ) { + return 'um-' . $item . '/um-' . $item . '.php'; + }, + $old_extensions + ); $active_plugins = UM()->dependencies()->get_active_plugins(); foreach ( $slugs as $slug ) { @@ -622,7 +658,7 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) { $arr_inactive_license_keys[] = $license->item_name; } - $invalid_license++; + ++$invalid_license; } if ( ! empty( $arr_inactive_license_keys ) ) { @@ -676,34 +712,41 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) {

- +  

- add_notice( 'upgrade', array( - 'class' => 'error', - 'message' => $message, - ), 4 ); - } else { - if ( isset( $_GET['msg'] ) && 'updated' === sanitize_key( $_GET['msg'] ) ) { - if ( isset( $_GET['page'] ) && 'um_options' === sanitize_key( $_GET['page'] ) ) { - $this->add_notice( 'settings_upgrade', array( - 'class' => 'updated', - 'message' => '

' . __( 'Settings successfully upgraded', 'ultimate-member' ) . '

', - ), 4 ); - } else { - $this->add_notice( - 'upgrade', - array( - 'class' => 'updated', - // translators: %1$s is a plugin name title; %2$s is a plugin version. - 'message' => '

' . sprintf( __( '%1$s %2$s Successfully Upgraded', 'ultimate-member' ), UM_PLUGIN_NAME, UM_VERSION ) . '

', - ), - 4 - ); - } + $this->add_notice( + 'upgrade', + array( + 'class' => 'error', + 'message' => $message, + ), + 4 + ); + } elseif ( isset( $_GET['msg'] ) && 'updated' === sanitize_key( $_GET['msg'] ) ) { + if ( isset( $_GET['page'] ) && 'um_options' === sanitize_key( $_GET['page'] ) ) { + $this->add_notice( + 'settings_upgrade', + array( + 'class' => 'updated', + 'message' => '

' . __( 'Settings successfully upgraded', 'ultimate-member' ) . '

', + ), + 4 + ); + } else { + $this->add_notice( + 'upgrade', + array( + 'class' => 'updated', + // translators: %1$s is a plugin name title; %2$s is a plugin version. + 'message' => '

' . sprintf( __( '%1$s %2$s Successfully Upgraded', 'ultimate-member' ), UM_PLUGIN_NAME, UM_VERSION ) . '

', + ), + 4 + ); } } } @@ -734,18 +777,18 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) { ?>

-  |  -  |  - +  |  +  |  +

- +

- +

@@ -754,7 +797,7 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) {

- +

@@ -763,17 +806,22 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) {

- +

- add_notice( 'reviews_notice', array( - 'class' => 'updated', - 'message' => $message, - 'dismissible' => true - ), 1 ); + $this->add_notice( + 'reviews_notice', + array( + 'class' => 'updated', + 'message' => $message, + 'dismissible' => true, + ), + 1 + ); } @@ -782,7 +830,8 @@ if ( ! class_exists( 'um\admin\core\Admin_Notices' ) ) { */ function future_changed() { - ob_start(); ?> + ob_start(); + ?>

- add_notice( 'future_changes', array( - 'class' => 'updated', - 'message' => $message, - ), 2 ); + $this->add_notice( + 'future_changes', + array( + 'class' => 'updated', + 'message' => $message, + ), + 2 + ); } /** diff --git a/includes/common/actions/class-users.php b/includes/common/actions/class-users.php index dae0ce7d..d034f3cf 100644 --- a/includes/common/actions/class-users.php +++ b/includes/common/actions/class-users.php @@ -20,16 +20,16 @@ if ( ! class_exists( 'um\common\actions\Users' ) ) { const INTERVAL = 3600; - const SCHEDULE_ACTION = 'um_schedule_account_status_check'; + const SCHEDULE_ACTION = 'um_schedule_empty_account_status_check'; const BATCH_SIZE = 50; - const BATCH_ACTION = 'um_check_account_status_batch'; + const BATCH_ACTION = 'um_set_default_account_status'; public function __construct() { add_action( 'init', array( &$this, 'add_recurring_action' ) ); add_action( self::SCHEDULE_ACTION, array( &$this, 'status_check' ) ); - add_action( self::BATCH_ACTION, array( &$this, 'batch_check' ) ); + add_action( self::BATCH_ACTION, array( &$this, 'batch_check' ), 10, 3 ); } public function add_recurring_action() { @@ -45,31 +45,34 @@ if ( ! class_exists( 'um\common\actions\Users' ) ) { } public function status_check() { - global $wpdb; - $total_users = $wpdb->get_var( - "SELECT COUNT(u.ID) - FROM {$wpdb->users} u - LEFT JOIN {$wpdb->usermeta} um ON u.ID = um.user_id AND um.meta_key = 'account_status' - LEFT JOIN {$wpdb->usermeta} um2 ON u.ID = um2.user_id AND um2.meta_key = 'um_registration_in_progress' - WHERE ( um.meta_value IS NULL OR um.meta_value = '' ) AND - um2.meta_value IS NULL OR um2.meta_value != '1'" - ); - $total_users = absint( $total_users ); - + $total_users = UM()->common()->users()::get_empty_status_users(); if ( empty( $total_users ) ) { return; } - for ( $offset = 0; $offset < $total_users; $offset += self::BATCH_SIZE ) { - UM()->maybe_action_scheduler()->enqueue_async_action( self::BATCH_ACTION, array( $offset ) ); - } + UM()->maybe_action_scheduler()->enqueue_async_action( + self::BATCH_ACTION, + array( + 'page' => 1, + 'total' => $total_users, + 'pages' => ceil( $total_users / self::BATCH_SIZE ), + ) + ); } - public function batch_check( $offset ) { + /** + * Perform batch checking for users based on specific conditions. + * Ignore users with `um_registration_in_progress` that can be in the process of the registration. + * Get users with empty `account_status` meta. + * + * @param int $page The current page number. + * @param int $total The total number of users to process. + * @param int $pages The total number of pages to process. + */ + public function batch_check( $page, $total, $pages ) { $users = new WP_User_Query( array( 'number' => self::BATCH_SIZE, - 'offset' => $offset, 'fields' => 'ids', 'meta_query' => array( 'relation' => 'AND', @@ -104,8 +107,34 @@ if ( ! class_exists( 'um\common\actions\Users' ) ) { $results = $users->get_results(); if ( ! empty( $results ) ) { + $um_empty_status_users = get_option( '_um_log_empty_status_users', array( 0, 0 ) ); + if ( ! is_array( $um_empty_status_users ) ) { + $um_empty_status_users = array( 0, count( $results ) ); + } + foreach ( $results as $user_id ) { - UM()->common()->users()->approve( $user_id, true, true ); + $res = UM()->common()->users()->approve( $user_id, true, true ); + if ( $res ) { + ++$um_empty_status_users[0]; + } + } + + if ( $um_empty_status_users[0] < $um_empty_status_users[1] ) { + update_option( '_um_log_empty_status_users', $um_empty_status_users ); + } else { + delete_option( '_um_log_empty_status_users' ); + } + + $next_page = $page + 1; + if ( $next_page <= $pages ) { + UM()->maybe_action_scheduler()->enqueue_async_action( + self::BATCH_ACTION, + array( + 'page' => $next_page, + 'total' => $total, + 'pages' => $pages, + ) + ); } } } diff --git a/includes/common/class-users.php b/includes/common/class-users.php index 61587997..f5d1f933 100644 --- a/includes/common/class-users.php +++ b/includes/common/class-users.php @@ -830,4 +830,29 @@ class Users { $user = WP_Session_Tokens::get_instance( $user_id ); $user->destroy_all(); } + + /** + * Retrieve the number of users with empty `account_status` usermeta. + * + * @return int + */ + public static function get_empty_status_users() { + global $wpdb; + + $total_users = $wpdb->get_var( + "SELECT COUNT(u.ID) + FROM {$wpdb->users} u + LEFT JOIN {$wpdb->usermeta} um ON u.ID = um.user_id AND um.meta_key = 'account_status' + LEFT JOIN {$wpdb->usermeta} um2 ON u.ID = um2.user_id AND um2.meta_key = 'um_registration_in_progress' + WHERE ( um.meta_value IS NULL OR um.meta_value = '' ) AND + um2.meta_value IS NULL OR um2.meta_value != '1'" + ); + + $total_users = absint( $total_users ); + // In WordPress, an underscore prefix before the option name (e.g., _my_option_name) is commonly used to indicate that the option is private. + // This option has a format: {updated_users}/{total_users_for_update}. + update_option( '_um_log_empty_status_users', array( 0, $total_users ) ); + + return $total_users; + } } diff --git a/includes/core/class-setup.php b/includes/core/class-setup.php index 27a342eb..eb6d37f4 100644 --- a/includes/core/class-setup.php +++ b/includes/core/class-setup.php @@ -310,37 +310,21 @@ KEY meta_value_indx (um_value(191)) * @since 2.4.2 */ public function set_default_user_status() { - $result = get_transient( 'um_count_users_unassigned' ); - if ( false === $result ) { - $args = array( - 'fields' => 'ids', - 'number' => 0, - 'meta_query' => array( - array( - 'key' => 'account_status', - 'compare' => 'NOT EXISTS', - ), - ), - 'um_custom_user_query' => true, - ); - - $users = new WP_User_Query( $args ); - if ( empty( $users ) || is_wp_error( $users ) ) { - $result = array(); - } else { - $result = $users->get_results(); - } - - set_transient( 'um_count_users_unassigned', $result, DAY_IN_SECONDS ); - } - - if ( empty( $result ) ) { + $total_users = UM()->common()->users()::get_empty_status_users(); + if ( empty( $total_users ) ) { return; } - foreach ( $result as $user_id ) { - update_user_meta( $user_id, 'account_status', 'approved' ); - } + // If there are some users without `account_status` then run the first async batch for update. + $batch_size = 50; // See the class constant value `\um\common\actions\Users::BATCH_ACTION`. + UM()->maybe_action_scheduler()->enqueue_async_action( + $batch_size, + array( + 'page' => 1, + 'total' => $total_users, + 'pages' => ceil( $total_users / $batch_size ), + ) + ); } /** diff --git a/includes/core/class-user.php b/includes/core/class-user.php index f8d70584..66baf197 100644 --- a/includes/core/class-user.php +++ b/includes/core/class-user.php @@ -640,7 +640,6 @@ if ( ! class_exists( 'um\core\User' ) ) { UM()->files()->remove_dir( UM()->files()->upload_temp ); UM()->files()->remove_dir( UM()->uploader()->get_upload_base_dir() . um_user( 'ID' ) . DIRECTORY_SEPARATOR ); - delete_transient( 'um_count_users_unassigned' ); delete_transient( 'um_count_users_pending_dot' ); } @@ -959,8 +958,6 @@ if ( ! class_exists( 'um\core\User' ) ) { /** This action is documented in ultimate-member/includes/common/um-actions-register.php */ do_action( 'um_user_register', $user_id, $_POST, null ); } - - delete_transient( 'um_count_users_unassigned' ); } diff --git a/tests/generate-empty-status-users.php b/tests/generate-empty-status-users.php new file mode 100644 index 00000000..55e26ae2 --- /dev/null +++ b/tests/generate-empty-status-users.php @@ -0,0 +1,34 @@ + $random_user_name, + 'user_pass' => 'q1q2q1q2', + 'user_email' => $random_user_email, + 'first_name' => $random_first_name, + 'last_name' => $random_last_name, + 'role' => 'subscriber', + ); + + $user_id = wp_insert_user( $userdata ); + + if ( is_wp_error( $user_id ) ) { + // Something went wrong, handle the error + var_dump( 'User creation failed: ' . $user_id->get_error_message() ); + } else { + var_dump( 'User creation complete: ID:' . $user_id . ' Username:' . $userdata['user_login'] ); + } +} + +exit; diff --git a/uninstall.php b/uninstall.php index 6cf0d554..5cf4e8ab 100644 --- a/uninstall.php +++ b/uninstall.php @@ -127,7 +127,7 @@ if ( ! empty( $delete_options ) ) { delete_transient( "um_count_users_{$status}" ); } delete_transient( 'um_count_users_pending_dot' ); - delete_transient( 'um_count_users_unassigned' ); + delete_transient( 'um_count_users_unassigned' ); // legacy but still need to delete while uninstall. //remove all users cache UM()->user()->remove_cache_all_users(); From 820ef35104a74e93a4ebfec80c3679e1c0bebd95 Mon Sep 17 00:00:00 2001 From: Mykyta Synelnikov Date: Tue, 15 Apr 2025 14:54:44 +0300 Subject: [PATCH 19/26] Fix user meta key for registration progress handling Updated the user meta key from `um_registration_in_progress` to `_um_registration_in_progress` across multiple files for consistency and proper functionality. This change ensures accurate checking and handling of users in the registration process. --- includes/common/actions/class-users.php | 6 +++--- includes/common/class-users.php | 2 +- includes/core/um-actions-register.php | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/includes/common/actions/class-users.php b/includes/common/actions/class-users.php index d034f3cf..e3309b6a 100644 --- a/includes/common/actions/class-users.php +++ b/includes/common/actions/class-users.php @@ -62,7 +62,7 @@ if ( ! class_exists( 'um\common\actions\Users' ) ) { /** * Perform batch checking for users based on specific conditions. - * Ignore users with `um_registration_in_progress` that can be in the process of the registration. + * Ignore users with `_um_registration_in_progress` that can be in the process of the registration. * Get users with empty `account_status` meta. * * @param int $page The current page number. @@ -79,11 +79,11 @@ if ( ! class_exists( 'um\common\actions\Users' ) ) { array( 'relation' => 'OR', array( - 'key' => 'um_registration_in_progress', + 'key' => '_um_registration_in_progress', 'compare' => 'NOT EXISTS', ), array( - 'key' => 'um_registration_in_progress', + 'key' => '_um_registration_in_progress', 'value' => '1', 'compare' => '!=', ), diff --git a/includes/common/class-users.php b/includes/common/class-users.php index f5d1f933..392c29d2 100644 --- a/includes/common/class-users.php +++ b/includes/common/class-users.php @@ -843,7 +843,7 @@ class Users { "SELECT COUNT(u.ID) FROM {$wpdb->users} u LEFT JOIN {$wpdb->usermeta} um ON u.ID = um.user_id AND um.meta_key = 'account_status' - LEFT JOIN {$wpdb->usermeta} um2 ON u.ID = um2.user_id AND um2.meta_key = 'um_registration_in_progress' + LEFT JOIN {$wpdb->usermeta} um2 ON u.ID = um2.user_id AND um2.meta_key = '_um_registration_in_progress' WHERE ( um.meta_value IS NULL OR um.meta_value = '' ) AND um2.meta_value IS NULL OR um2.meta_value != '1'" ); diff --git a/includes/core/um-actions-register.php b/includes/core/um-actions-register.php index 2459d56c..b30d43a9 100644 --- a/includes/core/um-actions-register.php +++ b/includes/core/um-actions-register.php @@ -169,7 +169,7 @@ function um_check_user_status( $user_id, $args, $form_data = null ) { */ do_action( "um_post_registration_{$registration_status}_hook", $user_id, $args, $form_data ); - delete_user_meta( $user_id, 'um_registration_in_progress' ); // Status is set. We can delete this marker. + delete_user_meta( $user_id, '_um_registration_in_progress' ); // Status is set. We can delete this marker. if ( is_null( $form_data ) || is_admin() ) { return; @@ -507,7 +507,7 @@ function um_submit_form_register( $args, $form_data ) { 'meta_input' => array( // It's used to ignore users who cannot finish the registration process in the scheduled tasks // to set 'approved' status to the users without `account_status` meta. - 'um_registration_in_progress' => true, + '_um_registration_in_progress' => true, ), ); From f89b29426a13ea86c1be17d809843881851f2d75 Mon Sep 17 00:00:00 2001 From: Mykyta Synelnikov Date: Tue, 15 Apr 2025 18:08:44 +0300 Subject: [PATCH 20/26] Add validation for banned and blacklisted custom fields. This update enhances security by introducing checks for banned and blacklisted meta keys in custom fields. It includes CSS updates for admin builder styles and ensures banned fields are flagged accurately in the site health tool. --- assets/css/admin/builder.css | 15 +++ assets/css/admin/builder.min.css | 2 +- assets/css/admin/builder.sass | 12 ++ includes/admin/class-site-health.php | 119 ++++++++++---------- includes/admin/core/class-admin-builder.php | 3 + includes/core/class-builtin.php | 8 +- includes/core/class-user.php | 3 - 7 files changed, 95 insertions(+), 67 deletions(-) diff --git a/assets/css/admin/builder.css b/assets/css/admin/builder.css index fe28aca1..83959bf3 100644 --- a/assets/css/admin/builder.css +++ b/assets/css/admin/builder.css @@ -495,6 +495,11 @@ line-height: 18px; margin: 0 0 10px 0; } +.um-admin-drag-col.cols-3 .um-admin-drag-fld-banned { + height: auto; + line-height: 18px; + margin: 0 0 10px 0; +} .um-admin-drag-col.cols-3 .um-admin-drag-fld-icons { float: none; position: absolute; @@ -600,6 +605,16 @@ color: #fff; } +.um-admin-drag-fld-banned { + float: left; + font-size: 13px; + height: 30px; + line-height: 30px; + margin: 0 0 0 20px; + font-weight: 400; + color: var(--um-blocks-error-color, #d92d20); +} + .um-admin-drag-fld-icons { float: right; } diff --git a/assets/css/admin/builder.min.css b/assets/css/admin/builder.min.css index 184ab039..83cc04a0 100644 --- a/assets/css/admin/builder.min.css +++ b/assets/css/admin/builder.min.css @@ -1 +1 @@ -@-webkit-keyframes um-ajax-spinning{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-moz-keyframes um-ajax-spinning{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-ms-keyframes um-ajax-spinning{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes um-ajax-spinning{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.um-admin-row-loading{position:absolute;width:100%;height:calc(100% + 30px);top:-30px;left:0;background:rgba(241,241,241,.6);z-index:999;display:none}.um-admin-row-loading>span{display:block;-webkit-transition:.1s opacity;-moz-transition:.1s opacity;-ms-transition:.1s opacity;-o-transition:.1s opacity;transition:.1s opacity;color:#c6c6c6!important;-webkit-animation:um-ajax-spinning 1.1s infinite linear;animation:um-ajax-spinning 1.1s infinite linear;border-top:.2em solid #000;border-right:.2em solid #000;border-bottom:.2em solid #000;border-left:.2em solid #c6c6c6;font-size:1.75em;-ms-transform:translateZ(0);transform:translateZ(0);border-radius:50%;width:40px;height:40px;margin:-20px 0 0 -20px;outline:0;padding:0;vertical-align:baseline;position:absolute;left:50%;top:50%}.um-admin-boxed-links{width:100%;text-align:center}.um-admin-boxed-links a{width:33.3%;float:left;border-left:1px solid #fff;border-right:1px solid #fff;padding:30px 25px!important;background:#f8f8f8;text-align:center;display:inline-block!important;font-size:14px;transition:all .2s linear;text-decoration:none;color:#666;margin:0!important;box-sizing:border-box!important;border-radius:2px;font-weight:600}.um-admin-boxed-links a:hover{background:#eee}.um-admin-boxed-links a.um-admin-activebg{background:#0085ba;color:#fff}.um-admin-boxed-links a.um-admin-activebg:hover{background:#0085ba;color:#fff}.um-admin-boxed-links i{font-size:28px;vertical-align:middle;margin:0 10px 0 0}.um-admin-half{float:left;width:48%}.um-admin-half select{box-sizing:border-box}.um-admin-half.um-admin-right{float:right}.um-admin-half p:first-child{margin-top:0}.um-admin-half .um{opacity:1;margin-bottom:0!important}.um-admin-tri{float:left;width:33%;position:relative}.um-admin-tri[data-select2-id]{position:relative}._heading_text[data-select2-id]{position:relative}.um-admin-error-block,.um-admin-success-block{display:none;width:100%;background:#c74a4a;border-radius:3px;color:#fff;box-sizing:border-box;position:relative;padding:12px;font-size:13px;line-height:1em!important;margin:0 0 16px 0}.um-admin-success-block{background:#7acf58}.um-admin-cur-condition-template{display:none}.dynamic-mce-content{display:none}.um-admin-btns{line-height:1.5em;margin:0 0 5px 0;display:flex;flex-direction:row;justify-content:flex-start;align-items:baseline;flex-wrap:wrap}.um-admin-btns .um-no-custom-fields{margin:0}.um-admin-btns a{margin:0 3px 8px 0!important;font-size:12px!important;display:flex;flex-direction:row;justify-content:flex-start;align-items:center;flex-wrap:nowrap;display:flex!important}.um-admin-btns a.with-icon span{color:#aaa;font-size:15px!important;padding-left:5px;height:auto;width:auto}.um-admin-btns a.with-icon:hover span{color:#cb3838}.um-admin-btn-toggle{padding:10px 0}.um-admin-btn-toggle p{margin:0 5px 0 0!important}.um-admin-btn-toggle p.um-admin-reset-conditions a{margin-bottom:5px}.um-admin-btn-toggle a{text-decoration:none;color:#999}.um-admin-btn-toggle a.um-admin-new-condition{margin-bottom:5px}.um-admin-btn-toggle a.active{color:#0085ba}.um-admin-btn-toggle a.active:hover{color:#0085ba}.um-admin-btn-toggle i{margin:0 5px 0 0!important;height:100%;color:#666;position:relative;top:1px}.um-admin-btn-content{display:none;padding:5px 0 0 0}.um-admin-btn-content p{float:left;margin-right:10px!important;padding:0!important}.um-admin-btn-content p.um-admin-conditions-notice{width:100%;margin:0 0 9px 0!important}.um-admin-btn-content .um-admin-cur-condition:not(:last-child){margin:0 0 5px 0}.um-admin-builder i{font-size:15px!important;height:100%!important;line-height:100%!important;margin:0 5px 0 0!important}.um-admin-builder .um-admin-drag{margin-top:40px}.um-admin-drag-add-field{display:block;width:100%;height:30px;line-height:30px;text-align:center;color:#aaa;text-decoration:none}.um-admin-drag-add-field:hover{color:#0085ba}.um-admin-drag-add-field i{font-size:23px!important;margin:0!important}.um-admin-drag-ctrls{position:absolute;top:-30px;left:0}.um-admin-drag-ctrls.um-admin-drag-ctrls-demo{position:absolute;right:7px;top:-30px;left:auto}.um-admin-drag-ctrls a{float:left;margin:0 5px 0 0;height:30px;line-height:30px;background:#eee;color:#888;padding:0 8px;text-decoration:none;border-radius:2px 2px 0 0}.um-admin-drag-ctrls a:hover{background-color:#e5e5e5}.um-admin-drag-ctrls a.active,.um-admin-drag-ctrls a.active:hover{background-color:#0085ba;color:#fff}.um-admin-drag-ctrls.columns a{width:30px!important;background-image:url(../../img/builder/1-column.gif);background-repeat:no-repeat;background-position:center}.um-admin-drag-ctrls.columns a:nth-child(2){background-image:url(../../img/builder/2-columns.gif)}.um-admin-drag-ctrls.columns a:last-child{background-image:url(../../img/builder/3-columns.gif)}.um-admin-drag-ctrls.columns a.active{background-image:url(../../img/builder/1-column-active.gif)}.um-admin-drag-ctrls.columns a.active:nth-child(2){background-image:url(../../img/builder/2-columns-active.gif)}.um-admin-drag-ctrls.columns a.active:last-child{background-image:url(../../img/builder/3-columns-active.gif)}.um-admin-drag{width:100%;background:#f1f1f1;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;position:relative;padding:60px 20px 20px 20px}.um-admin-drag-row{width:100%;background:#fefefe;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;position:relative;padding:60px 20px 20px 20px;margin:0 0 60px 0}.um-admin-drag-row:last-child{margin-bottom:0}.um-admin-drag-row-icons{position:absolute;right:0;top:-30px;height:30px}.um-admin-drag-row-icons a{text-decoration:none;color:#fff;width:40px;height:30px;line-height:30px;text-align:center;display:block!important;float:left;background:#0085ba;padding:0 4px;transition:all .2s linear;border-left:1px solid #379dd5}.um-admin-drag-row-icons a:hover{background:#008ec2}.um-admin-drag-row-icons span{cursor:move!important;text-decoration:none;color:#fff;width:40px;height:30px;line-height:30px;text-align:center;display:block!important;float:left;background:#0085ba;padding:0 4px;transition:all .2s linear;border-left:1px solid #379dd5}.um-admin-drag-row-icons span:hover{background:#008ec2}.um-admin-drag-row-icons i{font-size:18px!important;margin:0!important;top:2px;position:relative}.um-admin-drag-rowsub{position:relative;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;background:#f1f1f1;padding:20px;margin:0 0 60px 0}.um-admin-drag-rowsub:last-child{margin-bottom:0}.um-admin-drag-rowsub-icons{position:absolute;right:0;top:-30px;height:30px}.um-admin-drag-rowsub-icons a{text-decoration:none;color:#fff;width:40px;height:30px;line-height:30px;text-align:center;display:block!important;float:left;background:#0085ba;padding:0 4px;transition:all .2s linear;border-left:1px solid #379dd5}.um-admin-drag-rowsub-icons a:hover{background:#008ec2}.um-admin-drag-rowsub-icons span{cursor:move!important;text-decoration:none;color:#fff;width:40px;height:30px;line-height:30px;text-align:center;display:block!important;float:left;background:#0085ba;padding:0 4px;transition:all .2s linear;border-left:1px solid #379dd5}.um-admin-drag-rowsub-icons span:hover{background:#008ec2}.um-admin-drag-rowsub-icons i{font-size:18px!important;margin:0!important;top:2px;position:relative}.um-admin-drag-col{float:left;width:100%;background:#fff;border:1px dashed #bbb;box-sizing:border-box;padding:20px}.um-admin-drag-col.cols-3{width:32%}.um-admin-drag-col.cols-3.cols-last{width:32%}.um-admin-drag-col.cols-3.cols-middle{width:32%;margin:0 2%}.um-admin-drag-col.cols-3 .um-admin-drag-fld-title{height:auto;line-height:18px;margin:10px;margin-bottom:0}.um-admin-drag-col.cols-3 .um-admin-drag-fld-type{height:auto;line-height:18px;margin:0 0 10px 0}.um-admin-drag-col.cols-3 .um-admin-drag-fld-icons{float:none;position:absolute;bottom:0;right:0;height:30px;display:none}.um-admin-drag-col.cols-3 .um-admin-drag-fld-icons.um-field-type-group{float:left;position:relative;bottom:auto;right:auto;height:30px;display:block}.um-admin-drag-col.cols-3 .um-admin-drag-fld:hover .um-admin-drag-fld-icons{display:block}.um-admin-drag-col.cols-1{width:100%}.um-admin-drag-col.cols-1.cols-last{width:100%}.um-admin-drag-col.cols-2{width:49%;margin:0 1% 0 0}.um-admin-drag-col.cols-2.cols-last{width:49%;margin:0 0 0 1%}.um-admin-drag-col>.um-admin-drag-fld:last-of-type{margin-bottom:20px}.um-admin-drag-addrow{text-align:center;font-size:14px;color:#888;cursor:pointer;border:1px dashed #bbb;padding:10px 0;margin:20px 0 0 0}.um-admin-drag-addrow i{font-size:23px!important;margin:0!important}.um-admin-drag-addrow:hover{color:#0085ba}.um-admin-drag-fld{display:block;position:relative;border:1px solid #ddd;margin:0 0 15px 0;background:#f5f5f5;cursor:move!important}.um-admin-drag-fld.um-field-type-group{background:#555;border:0}.um-admin-drag-fld-title{float:left;font-weight:700;font-size:13px;color:#666;height:30px;line-height:30px;margin:0 0 0 20px}.um-admin-drag-fld-title.um-field-type-group{color:#fff}.um-admin-drag-fld-title i{width:24px;display:inline-block;text-align:center}.um-admin-drag-group{background:#fcfcfc;cursor:default;padding:20px;border:1px dashed #bbb}.um-admin-drag-fld-type{float:left;font-size:13px;height:30px;line-height:30px;margin:0 0 0 20px;font-weight:400;color:#999}.um-admin-drag-fld-type.um-field-type-group{color:#fff}.um-admin-drag-fld-icons{float:right}.um-admin-drag-fld-icons a{text-decoration:none;color:#fff;width:40px;height:30px;line-height:30px;text-align:center;display:block!important;float:left;background:#0085ba;padding:0 4px;transition:all .2s linear;border-left:1px solid #379dd5}.um-admin-drag-fld-icons a:hover{background:#008ec2}.um-admin-drag-fld-icons i{font-size:18px!important;margin:0!important;top:2px;position:relative}.um-admin-drag-fld-icons.um-field-type-group a.um_admin_duplicate_field{display:none!important}.um-row-placeholder{width:100%;border:2px dashed #aaa;box-sizing:border-box}.um-rowsub-placeholder{width:100%;border:2px dashed #ccc;box-sizing:border-box;display:block}.um-fld-placeholder{display:block;border:1px dashed #ddd;background:#fff;box-sizing:border-box;width:100%}.um-admin-modal #UM_preview_form .um-admin-modal-body{position:relative}.um-admin-modal #UM_preview_form .um-admin-modal-body .um-admin-preview-overlay{position:absolute;top:0;bottom:0;left:0;right:0;background-color:rgba(255,255,255,0);z-index:100} \ No newline at end of file +@-webkit-keyframes um-ajax-spinning{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-moz-keyframes um-ajax-spinning{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-ms-keyframes um-ajax-spinning{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes um-ajax-spinning{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.um-admin-row-loading{position:absolute;width:100%;height:calc(100% + 30px);top:-30px;left:0;background:rgba(241,241,241,.6);z-index:999;display:none}.um-admin-row-loading>span{display:block;-webkit-transition:.1s opacity;-moz-transition:.1s opacity;-ms-transition:.1s opacity;-o-transition:.1s opacity;transition:.1s opacity;color:#c6c6c6!important;-webkit-animation:um-ajax-spinning 1.1s infinite linear;animation:um-ajax-spinning 1.1s infinite linear;border-top:.2em solid #000;border-right:.2em solid #000;border-bottom:.2em solid #000;border-left:.2em solid #c6c6c6;font-size:1.75em;-ms-transform:translateZ(0);transform:translateZ(0);border-radius:50%;width:40px;height:40px;margin:-20px 0 0 -20px;outline:0;padding:0;vertical-align:baseline;position:absolute;left:50%;top:50%}.um-admin-boxed-links{width:100%;text-align:center}.um-admin-boxed-links a{width:33.3%;float:left;border-left:1px solid #fff;border-right:1px solid #fff;padding:30px 25px!important;background:#f8f8f8;text-align:center;display:inline-block!important;font-size:14px;transition:all .2s linear;text-decoration:none;color:#666;margin:0!important;box-sizing:border-box!important;border-radius:2px;font-weight:600}.um-admin-boxed-links a:hover{background:#eee}.um-admin-boxed-links a.um-admin-activebg{background:#0085ba;color:#fff}.um-admin-boxed-links a.um-admin-activebg:hover{background:#0085ba;color:#fff}.um-admin-boxed-links i{font-size:28px;vertical-align:middle;margin:0 10px 0 0}.um-admin-half{float:left;width:48%}.um-admin-half select{box-sizing:border-box}.um-admin-half.um-admin-right{float:right}.um-admin-half p:first-child{margin-top:0}.um-admin-half .um{opacity:1;margin-bottom:0!important}.um-admin-tri{float:left;width:33%;position:relative}.um-admin-tri[data-select2-id]{position:relative}._heading_text[data-select2-id]{position:relative}.um-admin-error-block,.um-admin-success-block{display:none;width:100%;background:#c74a4a;border-radius:3px;color:#fff;box-sizing:border-box;position:relative;padding:12px;font-size:13px;line-height:1em!important;margin:0 0 16px 0}.um-admin-success-block{background:#7acf58}.um-admin-cur-condition-template{display:none}.dynamic-mce-content{display:none}.um-admin-btns{line-height:1.5em;margin:0 0 5px 0;display:flex;flex-direction:row;justify-content:flex-start;align-items:baseline;flex-wrap:wrap}.um-admin-btns .um-no-custom-fields{margin:0}.um-admin-btns a{margin:0 3px 8px 0!important;font-size:12px!important;display:flex;flex-direction:row;justify-content:flex-start;align-items:center;flex-wrap:nowrap;display:flex!important}.um-admin-btns a.with-icon span{color:#aaa;font-size:15px!important;padding-left:5px;height:auto;width:auto}.um-admin-btns a.with-icon:hover span{color:#cb3838}.um-admin-btn-toggle{padding:10px 0}.um-admin-btn-toggle p{margin:0 5px 0 0!important}.um-admin-btn-toggle p.um-admin-reset-conditions a{margin-bottom:5px}.um-admin-btn-toggle a{text-decoration:none;color:#999}.um-admin-btn-toggle a.um-admin-new-condition{margin-bottom:5px}.um-admin-btn-toggle a.active{color:#0085ba}.um-admin-btn-toggle a.active:hover{color:#0085ba}.um-admin-btn-toggle i{margin:0 5px 0 0!important;height:100%;color:#666;position:relative;top:1px}.um-admin-btn-content{display:none;padding:5px 0 0 0}.um-admin-btn-content p{float:left;margin-right:10px!important;padding:0!important}.um-admin-btn-content p.um-admin-conditions-notice{width:100%;margin:0 0 9px 0!important}.um-admin-btn-content .um-admin-cur-condition:not(:last-child){margin:0 0 5px 0}.um-admin-builder i{font-size:15px!important;height:100%!important;line-height:100%!important;margin:0 5px 0 0!important}.um-admin-builder .um-admin-drag{margin-top:40px}.um-admin-drag-add-field{display:block;width:100%;height:30px;line-height:30px;text-align:center;color:#aaa;text-decoration:none}.um-admin-drag-add-field:hover{color:#0085ba}.um-admin-drag-add-field i{font-size:23px!important;margin:0!important}.um-admin-drag-ctrls{position:absolute;top:-30px;left:0}.um-admin-drag-ctrls.um-admin-drag-ctrls-demo{position:absolute;right:7px;top:-30px;left:auto}.um-admin-drag-ctrls a{float:left;margin:0 5px 0 0;height:30px;line-height:30px;background:#eee;color:#888;padding:0 8px;text-decoration:none;border-radius:2px 2px 0 0}.um-admin-drag-ctrls a:hover{background-color:#e5e5e5}.um-admin-drag-ctrls a.active,.um-admin-drag-ctrls a.active:hover{background-color:#0085ba;color:#fff}.um-admin-drag-ctrls.columns a{width:30px!important;background-image:url(../../img/builder/1-column.gif);background-repeat:no-repeat;background-position:center}.um-admin-drag-ctrls.columns a:nth-child(2){background-image:url(../../img/builder/2-columns.gif)}.um-admin-drag-ctrls.columns a:last-child{background-image:url(../../img/builder/3-columns.gif)}.um-admin-drag-ctrls.columns a.active{background-image:url(../../img/builder/1-column-active.gif)}.um-admin-drag-ctrls.columns a.active:nth-child(2){background-image:url(../../img/builder/2-columns-active.gif)}.um-admin-drag-ctrls.columns a.active:last-child{background-image:url(../../img/builder/3-columns-active.gif)}.um-admin-drag{width:100%;background:#f1f1f1;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;position:relative;padding:60px 20px 20px 20px}.um-admin-drag-row{width:100%;background:#fefefe;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;position:relative;padding:60px 20px 20px 20px;margin:0 0 60px 0}.um-admin-drag-row:last-child{margin-bottom:0}.um-admin-drag-row-icons{position:absolute;right:0;top:-30px;height:30px}.um-admin-drag-row-icons a{text-decoration:none;color:#fff;width:40px;height:30px;line-height:30px;text-align:center;display:block!important;float:left;background:#0085ba;padding:0 4px;transition:all .2s linear;border-left:1px solid #379dd5}.um-admin-drag-row-icons a:hover{background:#008ec2}.um-admin-drag-row-icons span{cursor:move!important;text-decoration:none;color:#fff;width:40px;height:30px;line-height:30px;text-align:center;display:block!important;float:left;background:#0085ba;padding:0 4px;transition:all .2s linear;border-left:1px solid #379dd5}.um-admin-drag-row-icons span:hover{background:#008ec2}.um-admin-drag-row-icons i{font-size:18px!important;margin:0!important;top:2px;position:relative}.um-admin-drag-rowsub{position:relative;box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;background:#f1f1f1;padding:20px;margin:0 0 60px 0}.um-admin-drag-rowsub:last-child{margin-bottom:0}.um-admin-drag-rowsub-icons{position:absolute;right:0;top:-30px;height:30px}.um-admin-drag-rowsub-icons a{text-decoration:none;color:#fff;width:40px;height:30px;line-height:30px;text-align:center;display:block!important;float:left;background:#0085ba;padding:0 4px;transition:all .2s linear;border-left:1px solid #379dd5}.um-admin-drag-rowsub-icons a:hover{background:#008ec2}.um-admin-drag-rowsub-icons span{cursor:move!important;text-decoration:none;color:#fff;width:40px;height:30px;line-height:30px;text-align:center;display:block!important;float:left;background:#0085ba;padding:0 4px;transition:all .2s linear;border-left:1px solid #379dd5}.um-admin-drag-rowsub-icons span:hover{background:#008ec2}.um-admin-drag-rowsub-icons i{font-size:18px!important;margin:0!important;top:2px;position:relative}.um-admin-drag-col{float:left;width:100%;background:#fff;border:1px dashed #bbb;box-sizing:border-box;padding:20px}.um-admin-drag-col.cols-3{width:32%}.um-admin-drag-col.cols-3.cols-last{width:32%}.um-admin-drag-col.cols-3.cols-middle{width:32%;margin:0 2%}.um-admin-drag-col.cols-3 .um-admin-drag-fld-title{height:auto;line-height:18px;margin:10px;margin-bottom:0}.um-admin-drag-col.cols-3 .um-admin-drag-fld-type{height:auto;line-height:18px;margin:0 0 10px 0}.um-admin-drag-col.cols-3 .um-admin-drag-fld-banned{height:auto;line-height:18px;margin:0 0 10px 0}.um-admin-drag-col.cols-3 .um-admin-drag-fld-icons{float:none;position:absolute;bottom:0;right:0;height:30px;display:none}.um-admin-drag-col.cols-3 .um-admin-drag-fld-icons.um-field-type-group{float:left;position:relative;bottom:auto;right:auto;height:30px;display:block}.um-admin-drag-col.cols-3 .um-admin-drag-fld:hover .um-admin-drag-fld-icons{display:block}.um-admin-drag-col.cols-1{width:100%}.um-admin-drag-col.cols-1.cols-last{width:100%}.um-admin-drag-col.cols-2{width:49%;margin:0 1% 0 0}.um-admin-drag-col.cols-2.cols-last{width:49%;margin:0 0 0 1%}.um-admin-drag-col>.um-admin-drag-fld:last-of-type{margin-bottom:20px}.um-admin-drag-addrow{text-align:center;font-size:14px;color:#888;cursor:pointer;border:1px dashed #bbb;padding:10px 0;margin:20px 0 0 0}.um-admin-drag-addrow i{font-size:23px!important;margin:0!important}.um-admin-drag-addrow:hover{color:#0085ba}.um-admin-drag-fld{display:block;position:relative;border:1px solid #ddd;margin:0 0 15px 0;background:#f5f5f5;cursor:move!important}.um-admin-drag-fld.um-field-type-group{background:#555;border:0}.um-admin-drag-fld-title{float:left;font-weight:700;font-size:13px;color:#666;height:30px;line-height:30px;margin:0 0 0 20px}.um-admin-drag-fld-title.um-field-type-group{color:#fff}.um-admin-drag-fld-title i{width:24px;display:inline-block;text-align:center}.um-admin-drag-group{background:#fcfcfc;cursor:default;padding:20px;border:1px dashed #bbb}.um-admin-drag-fld-type{float:left;font-size:13px;height:30px;line-height:30px;margin:0 0 0 20px;font-weight:400;color:#999}.um-admin-drag-fld-type.um-field-type-group{color:#fff}.um-admin-drag-fld-banned{float:left;font-size:13px;height:30px;line-height:30px;margin:0 0 0 20px;font-weight:400;color:var(--um-blocks-error-color,#d92d20)}.um-admin-drag-fld-icons{float:right}.um-admin-drag-fld-icons a{text-decoration:none;color:#fff;width:40px;height:30px;line-height:30px;text-align:center;display:block!important;float:left;background:#0085ba;padding:0 4px;transition:all .2s linear;border-left:1px solid #379dd5}.um-admin-drag-fld-icons a:hover{background:#008ec2}.um-admin-drag-fld-icons i{font-size:18px!important;margin:0!important;top:2px;position:relative}.um-admin-drag-fld-icons.um-field-type-group a.um_admin_duplicate_field{display:none!important}.um-row-placeholder{width:100%;border:2px dashed #aaa;box-sizing:border-box}.um-rowsub-placeholder{width:100%;border:2px dashed #ccc;box-sizing:border-box;display:block}.um-fld-placeholder{display:block;border:1px dashed #ddd;background:#fff;box-sizing:border-box;width:100%}.um-admin-modal #UM_preview_form .um-admin-modal-body{position:relative}.um-admin-modal #UM_preview_form .um-admin-modal-body .um-admin-preview-overlay{position:absolute;top:0;bottom:0;left:0;right:0;background-color:rgba(255,255,255,0);z-index:100} \ No newline at end of file diff --git a/assets/css/admin/builder.sass b/assets/css/admin/builder.sass index f1356be4..18621495 100644 --- a/assets/css/admin/builder.sass +++ b/assets/css/admin/builder.sass @@ -400,6 +400,10 @@ height: auto line-height: 18px margin: 0 0 10px 0 + .#{$prefix}admin-drag-fld-banned + height: auto + line-height: 18px + margin: 0 0 10px 0 .#{$prefix}admin-drag-fld-icons float: none position: absolute @@ -492,6 +496,14 @@ &.um-field-type-group color: #fff +.#{$prefix}admin-drag-fld-banned + float: left + font-size: 13px + height: 30px + line-height: 30px + margin: 0 0 0 20px + font-weight: 400 + color: $error-text .#{$prefix}admin-drag-fld-icons float: right diff --git a/includes/admin/class-site-health.php b/includes/admin/class-site-health.php index f8405802..bdcd8b9f 100644 --- a/includes/admin/class-site-health.php +++ b/includes/admin/class-site-health.php @@ -1,6 +1,8 @@ builtin()->blacklist_fields, UM()->user()->banned_keys ); - foreach ( $keys as $key ) { - if ( isset( $custom_fields[ $key ] ) ) { + foreach ( array_keys( $custom_fields ) as $key ) { + if ( self::field_is_banned( $key ) ) { $tests['direct']['um_banned_fields'] = array( 'label' => esc_html__( 'Are the banned custom fields?', 'ultimate-member' ), 'test' => array( $this, 'banned_fields_test' ), @@ -224,9 +226,11 @@ class Site_Health { return $result; } - public function get_banned_fields() { - $keys = array_merge( UM()->builtin()->blacklist_fields, UM()->user()->banned_keys ); + private static function field_is_banned( $metakey ) { + return UM()->user()->is_metakey_banned( $metakey ) || in_array( strtolower( $metakey ), UM()->builtin()->blacklist_fields, true ); + } + public function get_banned_fields() { $result = array( 'description' => '', 'actions' => '', @@ -252,14 +256,21 @@ class Site_Health { if ( empty( $field['metakey'] ) ) { continue; } - if ( in_array( $field['metakey'], $keys, true ) ) { - $break_forms[] = array( - 'id' => $form_id, - 'title' => get_the_title( $form_id ), - 'link' => get_edit_post_link( $form_id ), - 'key' => $field['metakey'], - ); - ++$forms_count; + + if ( self::field_is_banned( $field['metakey'] ) ) { + if ( ! array_key_exists( $form_id, $break_forms ) ) { + $break_forms[ $form_id ] = array( + 'title' => get_the_title( $form_id ), + 'link' => get_edit_post_link( $form_id ), + 'fields' => array( + $field['metakey'] => isset( $field['title'] ) ? $field['title'] : __( 'Unknown title', 'ultimate-member' ), + ), + ); + + ++$forms_count; + } else { + $break_forms[ $form_id ]['fields'][ $field['metakey'] ] = isset( $field['title'] ) ? $field['title'] : __( 'Unknown title', 'ultimate-member' ); + } } } } @@ -268,30 +279,26 @@ class Site_Health { if ( 0 < $forms_count ) { $result['description'] .= sprintf( '

%s

', - __( 'Your fields in the Ultimate Member Forms are banned.', 'ultimate-member' ) + __( 'Please note that some fields in your Ultimate Member Forms are currently on a restricted list that disallows their use. This is particularly related to the Ultimate Member Forms and their fields below.', 'ultimate-member' ) ); if ( ! empty( $break_forms ) ) { - $result['description'] .= sprintf( - '

%s', - __( 'Related to Ultimate Member Forms: ', 'ultimate-member' ) - ); + $forms_description = array(); + foreach ( $break_forms as $form_id => $form_data ) { + $fields = array(); + foreach ( $form_data['fields'] as $metakey => $field_title ) { + $fields[] = sprintf( __( '%s (%s)', 'ultimate-member' ), $field_title, $metakey ); + } - $form_links = array(); - foreach ( $break_forms as $break_form ) { - $form_links[] = sprintf( - '%s in %s (#ID: %s)', - esc_url( $break_form['link'] ), - esc_html__( 'field', 'ultimate-member' ) . ' "' . esc_html( $break_form['key'] ) . '"', - esc_html( $break_form['title'] ), - esc_html( $break_form['id'] ) - ); + $forms_description[] = '

' . sprintf( + __( 'Fields in %s (#ID: %s):', 'ultimate-member' ), + esc_url( $form_data['link'] ), + esc_html( $form_data['title'] ), + esc_html( $form_id ) + ) . '

  • ' . implode( '
  • ', $fields ) . '
'; } - $result['description'] .= sprintf( - '%s


', - implode( ', ', $form_links ) - ); + $result['description'] .= implode( ' ', $forms_description ); } $result['actions'] .= sprintf( @@ -301,12 +308,12 @@ class Site_Health { ); } - $result = apply_filters( 'um_get_banned_fields_result', $result, $keys ); + $result = apply_filters( 'um_get_banned_fields_result', $result ); if ( ! empty( $result['description'] ) ) { $result['description'] .= sprintf( '

%s

', - __( 'Using banned meta keys may break the website\'s functionality.', 'ultimate-member' ) + __( 'The using meta keys from restricted list in Ultimate Member Forms may break the website\'s functionality and is unsecure.', 'ultimate-member' ) ); } @@ -336,8 +343,8 @@ class Site_Health { $banned_fields = $this->get_banned_fields(); if ( false !== $banned_fields ) { $result['label'] = __( 'Some field from Ultimate Member forms has banned meta key', 'ultimate-member' ); - $result['status'] = 'recommended'; - $result['badge']['color'] = 'orange'; + $result['status'] = 'critical'; + $result['badge']['color'] = 'red'; $result['description'] = $banned_fields['description']; $result['actions'] = $banned_fields['actions']; } @@ -375,7 +382,7 @@ class Site_Health { } private function get_field_data( $info, $key, $field_key, $field ) { - $row = isset( $field['metakey'] ) ? false : true; + $row = array_key_exists( 'type', $field ) && 'row' === $field['type']; $title = $row ? __( 'Row: ', 'ultimate-member' ) . $field['id'] : __( 'Field: ', 'ultimate-member' ) . $field['metakey']; $field = array_map( array( &$this, 'array_map' ), $field ); $field_info = array( @@ -389,7 +396,7 @@ class Site_Health { } private function get_member_directories() { - $query = new \WP_Query(); + $query = new WP_Query(); $member_directories = $query->query( array( 'post_type' => 'um_directory', @@ -1762,10 +1769,8 @@ class Site_Health { ), ) ); - } - if ( array_key_exists( '_um_login_redirect_url', $rolemeta ) && 'redirect_url' === $rolemeta['_um_login_redirect_url'] ) { - if ( array_key_exists( '_um_pending_url', $rolemeta ) ) { + if ( 'redirect_url' === $rolemeta['_um_after_login'] && array_key_exists( '_um_login_redirect_url', $rolemeta ) ) { $info[ 'ultimate-member-' . $key ]['fields'] = array_merge( $info[ 'ultimate-member-' . $key ]['fields'], array( @@ -1795,10 +1800,8 @@ class Site_Health { ), ) ); - } - if ( 'redirect_url' === $rolemeta['_um_after_logout'] ) { - if ( array_key_exists( '_um_logout_redirect_url', $rolemeta ) ) { + if ( 'redirect_url' === $rolemeta['_um_after_logout'] && array_key_exists( '_um_logout_redirect_url', $rolemeta ) ) { $info[ 'ultimate-member-' . $key ]['fields'] = array_merge( $info[ 'ultimate-member-' . $key ]['fields'], array( @@ -1824,10 +1827,8 @@ class Site_Health { ), ) ); - } - if ( 'redirect_url' === $rolemeta['_um_after_delete'] ) { - if ( array_key_exists( '_um_delete_redirect_url', $rolemeta ) ) { + if ( 'redirect_url' === $rolemeta['_um_after_delete'] && array_key_exists( '_um_delete_redirect_url', $rolemeta ) ) { $info[ 'ultimate-member-' . $key ]['fields'] = array_merge( $info[ 'ultimate-member-' . $key ]['fields'], array( @@ -1841,17 +1842,15 @@ class Site_Health { } if ( ! empty( $rolemeta['wp_capabilities'] ) ) { - if ( array_key_exists( 'wp_capabilities', $rolemeta ) ) { - $info[ 'ultimate-member-' . $key ]['fields'] = array_merge( - $info[ 'ultimate-member-' . $key ]['fields'], - array( - 'um-wp_capabilities' => array( - 'label' => __( 'WP Capabilities', 'ultimate-member' ), - 'value' => $rolemeta['wp_capabilities'], - ), - ) - ); - } + $info[ 'ultimate-member-' . $key ]['fields'] = array_merge( + $info[ 'ultimate-member-' . $key ]['fields'], + array( + 'um-wp_capabilities' => array( + 'label' => __( 'WP Capabilities', 'ultimate-member' ), + 'value' => $rolemeta['wp_capabilities'], + ), + ) + ); } $info = apply_filters( 'um_debug_information_user_role', $info, $key ); @@ -2295,11 +2294,7 @@ class Site_Health { ) ); } else { - if ( isset( $options[ $field ] ) ) { - $sortby_label = $options[ $field ]; - } else { - $sortby_label = $field; - } + $sortby_label = isset( $options[ $field ] ) ? $options[ $field ] : $field; $info[ 'ultimate-member-directory-' . $key ]['fields'] = array_merge( $info[ 'ultimate-member-directory-' . $key ]['fields'], array( diff --git a/includes/admin/core/class-admin-builder.php b/includes/admin/core/class-admin-builder.php index e0b49050..2a433c9f 100644 --- a/includes/admin/core/class-admin-builder.php +++ b/includes/admin/core/class-admin-builder.php @@ -546,6 +546,9 @@ if ( ! class_exists( 'um\admin\core\Admin_Builder' ) ) {
+ user()->is_metakey_banned( $key ) || in_array( strtolower( $key ), UM()->builtin()->blacklist_fields, true ) ) { ?> +
+
diff --git a/includes/core/class-builtin.php b/includes/core/class-builtin.php index aa0fb5c0..08ea9bbd 100644 --- a/includes/core/class-builtin.php +++ b/includes/core/class-builtin.php @@ -34,6 +34,9 @@ if ( ! class_exists( 'um\core\Builtin' ) ) { public $saved_fields = array(); /** + * Field keys that cannot be used as metakey when add new custom field to UM Form. + * Note: They are used both with function `UM()->user()->is_metakey_banned()` to avoid using unsecure metakeys. + * * @var array */ public $blacklist_fields = array(); @@ -183,7 +186,7 @@ if ( ! class_exists( 'um\core\Builtin' ) ) { * @return int|string Empty or error string. */ public function blacklist_field_err( $key ) { - if ( in_array( strtolower( $key ), $this->blacklist_fields, true ) || in_array( strtolower( $key ), UM()->user()->banned_keys, true ) ) { + if ( in_array( strtolower( $key ), $this->blacklist_fields, true ) || UM()->user()->is_metakey_banned( $key ) ) { return __( 'Your meta key can not be used', 'ultimate-member' ); } @@ -1426,6 +1429,9 @@ if ( ! class_exists( 'um\core\Builtin' ) ) { public function set_blacklist_fields() { $this->blacklist_fields = array( 'id', + 'role', + 'user_id', + 'user_pass', ); /** diff --git a/includes/core/class-user.php b/includes/core/class-user.php index ea358262..f8d70584 100644 --- a/includes/core/class-user.php +++ b/includes/core/class-user.php @@ -93,9 +93,6 @@ if ( ! class_exists( 'um\core\User' ) ) { $this->banned_keys = array( 'metabox', - 'user_id', - 'role', - 'user_pass', 'postbox', 'meta-box', 'dismissed_wp_pointers', From e99f158d184009b8635f9cfed785dd46c8a48533 Mon Sep 17 00:00:00 2001 From: Mykyta Synelnikov Date: Thu, 17 Apr 2025 20:02:28 +0300 Subject: [PATCH 21/26] Fixed #1677 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update country name from "Turkey" to "Türkiye" Replaced the outdated reference to "Turkey" with "Türkiye" in the Ultimate Member plugin's built-in country list. This aligns with the country's preferred naming convention. --- includes/core/class-builtin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/core/class-builtin.php b/includes/core/class-builtin.php index 08ea9bbd..76585731 100644 --- a/includes/core/class-builtin.php +++ b/includes/core/class-builtin.php @@ -2062,7 +2062,7 @@ if ( ! class_exists( 'um\core\Builtin' ) ) { 'TO' => __('Tonga','ultimate-member'), 'TT' => __('Trinidad and Tobago','ultimate-member'), 'TN' => __('Tunisia','ultimate-member'), - 'TR' => __('Turkey','ultimate-member'), + 'TR' => __('Türkiye','ultimate-member'), 'TM' => __('Turkmenistan','ultimate-member'), 'TC' => __('Turks and Caicos Islands','ultimate-member'), 'TV' => __('Tuvalu','ultimate-member'), From 5fdf822183e20071f4c60f9ea0c7b3780c72bafa Mon Sep 17 00:00:00 2001 From: Mykyta Synelnikov Date: Mon, 21 Apr 2025 23:30:03 +0300 Subject: [PATCH 22/26] Refactor version comparison logic in Ultimate Member. Enhanced the `compare_versions` method to include a new `$raw` parameter for conditional handling. Improved readability, added additional checks, and updated translator comments to ensure compatibility and clear messaging. --- includes/class-dependencies.php | 63 +++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 22 deletions(-) diff --git a/includes/class-dependencies.php b/includes/class-dependencies.php index 51e142a9..cc02622c 100644 --- a/includes/class-dependencies.php +++ b/includes/class-dependencies.php @@ -180,27 +180,41 @@ if ( ! class_exists( 'um\Dependencies' ) ) { /** * Compare UM core and extension versions * - * @param string $um_required_ver + * @param string $um_required_ver UM core required version. * @param string $ext_ver * @param string $ext_key * @param string $ext_title + * @param bool $raw False by default. Then return message. Use `true` in conditions inside `plugins_loaded` hook * @return bool */ - public function compare_versions( $um_required_ver, $ext_ver, $ext_key, $ext_title ) { - - if ( version_compare( UM_VERSION, $um_required_ver, '<' ) - || empty( $this->ext_required_version[$ext_key] ) - || version_compare( $this->ext_required_version[$ext_key], $ext_ver, '>' ) ) { + public function compare_versions( $um_required_ver, $ext_ver, $ext_key, $ext_title, $raw = false ) { + if ( empty( $this->ext_required_version[ $ext_key ] ) || + version_compare( UM_VERSION, $um_required_ver, '<' ) || + version_compare( $this->ext_required_version[ $ext_key ], $ext_ver, '>' ) ) { $message = ''; if ( version_compare( UM_VERSION, $um_required_ver, '<' ) ) { - // translators: %1$s is a extension name; %2$s is a plugin name; %3$s is a required version. - $message = sprintf( __( 'This version of "%1$s" requires the core %2$s plugin to be %3$s or higher.', 'ultimate-member' ), $ext_title, UM_PLUGIN_NAME, $um_required_ver ) . - '
' . - // translators: %s: plugin name. - sprintf( __( 'Please update %s to the latest version.', 'ultimate-member' ), UM_PLUGIN_NAME ); + if ( $raw ) { + return false; + } + if ( UM()->is_new_ui() ) { + // translators: %1$s is an extension name; %2$s is a plugin name; %3$s is a required version. + $message = sprintf( __( 'When new UI is enabled this version of "%1$s" requires the core %2$s plugin to be %3$s or higher.', 'ultimate-member' ), $ext_title, UM_PLUGIN_NAME, $um_required_ver ) . + '
' . + // translators: %s: plugin name. + sprintf( __( 'Please update %s to the latest version or disable new UI.', 'ultimate-member' ), UM_PLUGIN_NAME ); + } else { + // translators: %1$s is an extension name; %2$s is a plugin name; %3$s is a required version. + $message = sprintf( __( 'This version of "%1$s" requires the core %2$s plugin to be %3$s or higher.', 'ultimate-member' ), $ext_title, UM_PLUGIN_NAME, $um_required_ver ) . + '
' . + // translators: %s: plugin name. + sprintf( __( 'Please update %s to the latest version.', 'ultimate-member' ), UM_PLUGIN_NAME ); + } } elseif ( empty( $this->ext_required_version[ $ext_key ] ) || version_compare( $this->ext_required_version[ $ext_key ], $ext_ver, '>' ) ) { - // translators: %1$s is a plugin name; %2$s is a extension name; %3$s is a extension version. + if ( $raw ) { + return false; + } + // translators: %1$s is a plugin name; %2$s is an extension name; %3$s is an extension version. $message = sprintf( __( 'Sorry, but this version of %1$s does not work with extension "%2$s" %3$s version.', 'ultimate-member' ), UM_PLUGIN_NAME, $ext_title, $ext_ver ) . '
' . // translators: %s: extension name. @@ -208,19 +222,24 @@ if ( ! class_exists( 'um\Dependencies' ) ) { } return $message; - } else { - //check correct folder name for extensions - if ( ! self::$active_plugins ) self::init(); + } - if ( ! in_array( "um-{$ext_key}/um-{$ext_key}.php", self::$active_plugins ) && ! array_key_exists( "um-{$ext_key}/um-{$ext_key}.php", self::$active_plugins ) ) { - // translators: %1$s is a extension name; %2$s is a extension version. - $message = sprintf( __( 'Please check "%1$s" %2$s extension\'s folder name.', 'ultimate-member' ), $ext_title, $ext_ver ) . - '
' . - // translators: %s: extension name. - sprintf( __( 'Correct folder name is "%s"', 'ultimate-member' ), "um-{$ext_key}" ); + // Check correct folder name for extensions. + if ( ! self::$active_plugins ) { + self::init(); + } - return $message; + if ( ! array_key_exists( "um-{$ext_key}/um-{$ext_key}.php", self::$active_plugins ) && + ! in_array( "um-{$ext_key}/um-{$ext_key}.php", self::$active_plugins, true ) ) { + if ( $raw ) { + return false; } + + // translators: %1$s is an extension name; %2$s is an extension version. + return sprintf( __( 'Please check "%1$s" %2$s extension\'s folder name.', 'ultimate-member' ), $ext_title, $ext_ver ) . + '
' . + // translators: %s: extension name. + sprintf( __( 'Correct folder name is "%s"', 'ultimate-member' ), "um-{$ext_key}" ); } return true; From 54deffd24498dbce6b681246582bb0c6cc1790f8 Mon Sep 17 00:00:00 2001 From: Mykyta Synelnikov Date: Tue, 22 Apr 2025 17:16:55 +0300 Subject: [PATCH 23/26] Enhance password reset logic and add Action Scheduler filter Ensure proper handling of user data in password reset functions by adding checks and updating parameter handling. Introduce a new filter to extend site health information and include a setting for enabling email sending via Action Scheduler. Improve code clarity with updated comments and function annotations. --- includes/action-scheduler/class-init.php | 13 +++++++++++++ includes/admin/class-site-health.php | 14 +++++++++++++- includes/core/class-password.php | 6 +++++- includes/core/class-user.php | 15 ++++++++++----- 4 files changed, 41 insertions(+), 7 deletions(-) diff --git a/includes/action-scheduler/class-init.php b/includes/action-scheduler/class-init.php index 0c2356fe..27974be6 100644 --- a/includes/action-scheduler/class-init.php +++ b/includes/action-scheduler/class-init.php @@ -43,6 +43,7 @@ if ( ! class_exists( 'um\action_scheduler\Init' ) ) { public function __construct() { if ( ! $this->can_be_active() ) { add_action( 'init', array( $this, 'add_notice' ) ); + add_filter( 'um_site_health_extend', array( $this, 'change_site_health' ) ); } else { add_filter( 'um_settings_structure', array( $this, 'add_setting' ) ); // Since 2.10.3 we load library as soon as there are required library files. @@ -67,6 +68,18 @@ if ( ! class_exists( 'um\action_scheduler\Init' ) ) { ); } + /** + * Adds the Action Scheduler setting to Ultimate Member feature settings + * + * @param array $settings + * + * @return array + */ + public function change_site_health( $site_health ) { + $site_health['ultimate-member']['fields']['enable_as_email_sending']['value'] = __( 'No', 'ultimate-member' ); + return $site_health; + } + /** * Adds the Action Scheduler setting to Ultimate Member feature settings * diff --git a/includes/admin/class-site-health.php b/includes/admin/class-site-health.php index bdcd8b9f..7a8b7f1b 100644 --- a/includes/admin/class-site-health.php +++ b/includes/admin/class-site-health.php @@ -1238,6 +1238,10 @@ class Site_Health { 'label' => __( 'Remove Data on Uninstall?', 'ultimate-member' ), 'value' => UM()->options()->get( 'uninstall_on_delete' ) ? $labels['yes'] : $labels['no'], ), + 'enable_as_email_sending' => array( + 'label' => __( 'Email sending by Action Scheduler', 'ultimate-member' ), + 'value' => UM()->options()->get( 'enable_as_email_sending' ) ? $labels['yes'] : $labels['no'], + ), ); // Secure settings @@ -2549,6 +2553,14 @@ class Site_Health { } } - return $info; + /** + * Filters the site health information. + * @hook um_site_health_extend + * @since 2.10.3 + * @param {array} info - The site health info to be filtered. + * + * @return {array} The filtered site health info. + */ + return apply_filters( 'um_site_health_extend', $info ); } } diff --git a/includes/core/class-password.php b/includes/core/class-password.php index 5690e046..fcecc441 100644 --- a/includes/core/class-password.php +++ b/includes/core/class-password.php @@ -48,6 +48,10 @@ if ( ! class_exists( 'um\core\Password' ) ) { // 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 ); + if ( empty( $user_data ) ) { + return ''; + } + if ( empty( $reset_key ) ) { $reset_key = UM()->user()->maybe_generate_password_reset_key( $user_data ); } @@ -466,7 +470,7 @@ if ( ! class_exists( 'um\core\Password' ) ) { um_fetch_user( $data->ID ); if ( false === UM()->options()->get( 'only_approved_user_reset_password' ) || UM()->common()->users()->has_status( $data->ID, 'approved' ) ) { - UM()->user()->password_reset(); + UM()->user()->password_reset( $data->ID ); } } diff --git a/includes/core/class-user.php b/includes/core/class-user.php index 66baf197..6053eefd 100644 --- a/includes/core/class-user.php +++ b/includes/core/class-user.php @@ -1484,22 +1484,27 @@ if ( ! class_exists( 'um\core\User' ) ) { return get_password_reset_key( $userdata ); } - /** * Password reset email + * + * @param int|null $user_id + * + * @return void */ - function password_reset() { - $userdata = get_userdata( um_user( 'ID' ) ); + public function password_reset( $user_id = null ) { + if ( is_null( $user_id ) ) { + $user_id = um_user( 'ID' ); + } + $userdata = get_userdata( $user_id ); $this->maybe_generate_password_reset_key( $userdata ); add_filter( 'um_template_tags_patterns_hook', array( UM()->password(), 'add_placeholder' ) ); add_filter( 'um_template_tags_replaces_hook', array( UM()->password(), 'add_replace_placeholder' ) ); - UM()->maybe_action_scheduler()->enqueue_async_action( 'um_dispatch_email', array( $userdata->user_email, 'resetpw_email', array( 'fetch_user_id' => um_user( 'ID' ) ) ) ); + UM()->maybe_action_scheduler()->enqueue_async_action( 'um_dispatch_email', array( $userdata->user_email, 'resetpw_email', array( 'fetch_user_id' => $user_id ) ) ); } - /** * Password changed email * From db0da58351e39af71dfb6c4f8639be2c918a725a Mon Sep 17 00:00:00 2001 From: Mykyta Synelnikov Date: Wed, 23 Apr 2025 14:35:38 +0300 Subject: [PATCH 24/26] Refactor password reset URL handling and email dispatch. Added optional parameter to `reset_url` for user-specific handling. Updated email dispatch logic to include dynamic generation of the password reset link with proper placeholder replacements. --- includes/core/class-password.php | 10 +++++++--- includes/core/class-user.php | 13 ++++++++++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/includes/core/class-password.php b/includes/core/class-password.php index fcecc441..355f9183 100644 --- a/includes/core/class-password.php +++ b/includes/core/class-password.php @@ -36,12 +36,16 @@ if ( ! class_exists( 'um\core\Password' ) ) { /** * Get Reset URL * - * @return bool|string + * @param int|null $user_id + * + * @return string */ - public function reset_url() { + public function reset_url( $user_id = null ) { static $reset_key = null; - $user_id = um_user( 'ID' ); + if ( is_null( $user_id ) ) { + $user_id = um_user( 'ID' ); + } delete_option( "um_cache_userdata_{$user_id}" ); diff --git a/includes/core/class-user.php b/includes/core/class-user.php index 6053eefd..630bc515 100644 --- a/includes/core/class-user.php +++ b/includes/core/class-user.php @@ -1499,10 +1499,17 @@ if ( ! class_exists( 'um\core\User' ) ) { $this->maybe_generate_password_reset_key( $userdata ); - add_filter( 'um_template_tags_patterns_hook', array( UM()->password(), 'add_placeholder' ) ); - add_filter( 'um_template_tags_replaces_hook', array( UM()->password(), 'add_replace_placeholder' ) ); + $mail_args = array( + 'fetch_user_id' => $user_id, + 'tags' => array( + '{password_reset_link}', + ), + 'tags_replace' => array( + UM()->password()->reset_url( $user_id ), + ), + ); - UM()->maybe_action_scheduler()->enqueue_async_action( 'um_dispatch_email', array( $userdata->user_email, 'resetpw_email', array( 'fetch_user_id' => $user_id ) ) ); + UM()->maybe_action_scheduler()->enqueue_async_action( 'um_dispatch_email', array( $userdata->user_email, 'resetpw_email', $mail_args ) ); } /** From c6859072a0a88f938c7c06f12e1aeef7c137526d Mon Sep 17 00:00:00 2001 From: Mykyta Synelnikov Date: Wed, 23 Apr 2025 16:05:34 +0300 Subject: [PATCH 25/26] Update to version 2.10.3 with enhancements and bug fixes Added new settings for registration management and improved Action Scheduler flexibility. Resolved issues with Member Directory styling, filtering, and email placeholders. Updated terminology and documentation; ensure cached assets are refreshed post-update. --- changelog.txt | 18 ++++++++++++++++++ includes/admin/class-site-health.php | 5 ++++- readme.txt | 4 +++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index de589987..020e8c31 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,23 @@ == Changelog == += 2.10.3 April 24, 2025 = + +* 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 April 02, 2025 = * Enhancements: diff --git a/includes/admin/class-site-health.php b/includes/admin/class-site-health.php index 7a8b7f1b..80459933 100644 --- a/includes/admin/class-site-health.php +++ b/includes/admin/class-site-health.php @@ -2555,9 +2555,12 @@ class Site_Health { /** * Filters the site health information. + * * @hook um_site_health_extend + * * @since 2.10.3 - * @param {array} info - The site health info to be filtered. + * + * @param {array} $info The site health info to be filtered. * * @return {array} The filtered site health info. */ diff --git a/readme.txt b/readme.txt index 717c7564..53fa0b0c 100644 --- a/readme.txt +++ b/readme.txt @@ -167,7 +167,7 @@ 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.10.3 2025-04-15 = += 2.10.3 2025-04-24 = **Enhancements** @@ -180,6 +180,8 @@ IMPORTANT: PLEASE UPDATE THE PLUGIN TO AT LEAST VERSION 2.6.7 IMMEDIATELY. VERSI * 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** From f1f1bddeafb4fb163deffb25e04cfc59a4d0e5be Mon Sep 17 00:00:00 2001 From: Mykyta Synelnikov Date: Wed, 23 Apr 2025 16:13:27 +0300 Subject: [PATCH 26/26] Add new settings fields to Site Health info Introduce fields for "Ignore User Role Registration Options" and re-add "Email sending by Action Scheduler" under the Site Health settings. This improves the clarity and organization of user options in the admin interface. --- includes/admin/class-site-health.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/includes/admin/class-site-health.php b/includes/admin/class-site-health.php index 80459933..45ceec2d 100644 --- a/includes/admin/class-site-health.php +++ b/includes/admin/class-site-health.php @@ -514,6 +514,10 @@ class Site_Health { 'label' => __( 'Use Gravatars?', 'ultimate-member' ), 'value' => UM()->options()->get( 'use_gravatars' ) ? $labels['yes'] : $labels['no'], ), + 'um-admin_ignore_user_status' => array( + 'label' => __( 'Ignore the "User Role > Registration Options" if this user is added from the wp-admin dashboard', 'ultimate-member' ), + 'value' => UM()->options()->get( 'admin_ignore_user_status' ) ? $labels['yes'] : $labels['no'], + ), 'um-delete_comments' => array( 'label' => __( 'Deleting user comments after deleting a user', 'ultimate-member' ), 'value' => UM()->options()->get( 'delete_comments' ) ? $labels['yes'] : $labels['no'], @@ -1222,6 +1226,10 @@ class Site_Health { 'label' => __( 'Enable Gutenberg Blocks', 'ultimate-member' ), 'value' => UM()->options()->get( 'enable_blocks' ) ? $labels['yes'] : $labels['no'], ), + 'um-enable_as_email_sending' => array( + 'label' => __( 'Email sending by Action Scheduler', 'ultimate-member' ), + 'value' => UM()->options()->get( 'enable_as_email_sending' ) ? $labels['yes'] : $labels['no'], + ), 'um-rest_api_version' => array( 'label' => __( 'REST API version', 'ultimate-member' ), 'value' => UM()->options()->get( 'rest_api_version' ), @@ -1238,10 +1246,6 @@ class Site_Health { 'label' => __( 'Remove Data on Uninstall?', 'ultimate-member' ), 'value' => UM()->options()->get( 'uninstall_on_delete' ) ? $labels['yes'] : $labels['no'], ), - 'enable_as_email_sending' => array( - 'label' => __( 'Email sending by Action Scheduler', 'ultimate-member' ), - 'value' => UM()->options()->get( 'enable_as_email_sending' ) ? $labels['yes'] : $labels['no'], - ), ); // Secure settings